Skip to content
This repository has been archived by the owner on Aug 11, 2024. It is now read-only.

Commit

Permalink
Allow user-configuration of Noosphere log detail (#1016)
Browse files Browse the repository at this point in the history
1. Allow user configuration of Noosphere log detail (persisted using
AppDefaults)
2. Pipe `stdout` to a logger to capture Noosphere logs

This is per-process on iOS and you can see the result via Console.app

Intended to help with debugging
#1008
  • Loading branch information
bfollington authored Jan 16, 2024
1 parent 86b3c91 commit 21d3b08
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 2 deletions.
24 changes: 23 additions & 1 deletion xcode/Subconscious/Shared/Components/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ enum AppAction: Hashable {

/// Set and persist experimental block editor enabled
case persistBlockEditorEnabled(Bool)
case persistNoosphereLogLevel(Noosphere.NoosphereLogLevel)

/// Reset Noosphere Service.
/// This calls `Noosphere.reset` which resets memoized instances of
Expand Down Expand Up @@ -554,6 +555,7 @@ struct AppModel: ModelProtocol {

/// Is experimental block editor enabled?
var isBlockEditorEnabled = false
var noosphereLogLevel: Noosphere.NoosphereLogLevel = .basic

/// Should recovery mode be presented?
var isRecoveryModePresented = false
Expand Down Expand Up @@ -884,6 +886,12 @@ struct AppModel: ModelProtocol {
environment: environment,
isBlockEditorEnabled: isBlockEditorEnabled
)
case let .persistNoosphereLogLevel(level):
return persistNoosphereLogLevel(
state: state,
environment: environment,
level: level
)
case .resetNoosphereService:
return resetNoosphereService(
state: state,
Expand Down Expand Up @@ -1343,6 +1351,7 @@ struct AppModel: ModelProtocol {
model.gatewayId = AppDefaults.standard.gatewayId
model.inviteCode = InviteCode(AppDefaults.standard.inviteCode ?? "")
model.selectedAppTab = AppTab(rawValue: AppDefaults.standard.selectedAppTab) ?? state.selectedAppTab
model.noosphereLogLevel = Noosphere.NoosphereLogLevel(description: AppDefaults.standard.noosphereLogLevel)
model.isBlockEditorEnabled = AppDefaults.standard.isBlockEditorEnabled

// Update model from app defaults
Expand Down Expand Up @@ -1724,6 +1733,18 @@ struct AppModel: ModelProtocol {
return Update(state: model)
}

static func persistNoosphereLogLevel(
state: AppModel,
environment: AppEnvironment,
level: Noosphere.NoosphereLogLevel
) -> Update<AppModel> {
// Persist value
AppDefaults.standard.noosphereLogLevel = level.description
var model = state
model.noosphereLogLevel = level
return Update(state: model)
}

static func requestOfflineMode(
state: AppModel,
environment: AppEnvironment
Expand Down Expand Up @@ -3151,14 +3172,15 @@ struct AppEnvironment {
)
let defaultGateway = GatewayURL(AppDefaults.standard.gatewayURL)
let defaultSphereIdentity = AppDefaults.standard.sphereIdentity
let defaultLogLevel = Noosphere.NoosphereLogLevel(description: AppDefaults.standard.noosphereLogLevel)

let sentry = SentryIntegration()

// If we're in debug, we want detailed logs from Noosphere.
let noosphereLogLevel: Noosphere.NoosphereLogLevel = (
Config.default.debug ?
.academic :
.basic
defaultLogLevel
)

let noosphere = NoosphereService(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ struct DeveloperSettingsView: View {
}
)
}
Section {
Picker("Noosphere log detail", selection: app.binding(
get: \.noosphereLogLevel,
tag: AppAction.persistNoosphereLogLevel
)) {
ForEach(Noosphere.NoosphereLogLevel.allCases, id: \.self) { level in
Text(level.description).tag(level)
}
}
.pickerStyle(DefaultPickerStyle())
}
}
.navigationTitle("Developer")
}
Expand Down
39 changes: 39 additions & 0 deletions xcode/Subconscious/Shared/Library/NoosphereLogProxy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//
// NoosphereLogProxy.swift
// Subconscious
//
// Created by Ben Follington on 20/12/2023.
//

import Foundation
import os
import OSLog

public enum NoosphereLogProxy {}

extension NoosphereLogProxy {
public static let pipe = Pipe()
static let logger = Logger(
subsystem: Config.default.rdns,
category: "StdOut"
)

/// Redirect STDOUT to a logger to capture it in production
public static func connect() -> Void {
// Adapted from https://stackoverflow.com/a/54124239
setvbuf(stdout, nil, _IONBF, 0)
dup2(pipe.fileHandleForWriting.fileDescriptor, STDOUT_FILENO)

pipe.fileHandleForReading.readabilityHandler = { handle in
let data = handle.availableData
let str = String(
data: data,
encoding: .ascii
) ?? "<Non-ascii data of size\(data.count)>\n"
DispatchQueue.main.async {
logger.log("\(str, privacy: .public)")
}
}
}
}

3 changes: 3 additions & 0 deletions xcode/Subconscious/Shared/Services/AppDefaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@ struct AppDefaults {
// enums must be serialized when stored as AppDefaults:
// https://cocoacasts.com/ud-6-how-to-store-an-enum-in-user-defaults-in-swift
var selectedAppTab: String = AppTab.notebook.rawValue

@UserDefaultsProperty(forKey: "noosphereLogLevel")
var noosphereLogLevel: String = Noosphere.NoosphereLogLevel.basic.description
}
50 changes: 49 additions & 1 deletion xcode/Subconscious/Shared/Services/Noosphere/Noosphere.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public struct SphereReceipt:
/// DB pointers, key storage interfaces, active HTTP clients etc.
public actor Noosphere {
/// Wraps `NS_NOOSPHERE_LOG_*` constants
enum NoosphereLogLevel: UInt32 {
enum NoosphereLogLevel: UInt32, CaseIterable {
/// Equivalent to minimal format / INFO filter
case basic
/// Equivalent to minimal format / DEBUG filter
Expand All @@ -77,6 +77,54 @@ public actor Noosphere {
/// Equivalent to pretty format / TRACE filter
case deafening

private static let basicDescription = "basic"
private static let chattyDescription = "chatty"
private static let silentDescription = "silent"
private static let academicDescription = "academic"
private static let informedDescription = "informed"
private static let tiresomeDescription = "tiresome"
private static let deafeningDescription = "deafening"

init(description: String) {
switch description {
case NoosphereLogLevel.basicDescription:
self = .basic
case NoosphereLogLevel.chattyDescription:
self = .chatty
case NoosphereLogLevel.silentDescription:
self = .silent
case NoosphereLogLevel.academicDescription:
self = .academic
case NoosphereLogLevel.informedDescription:
self = .informed
case NoosphereLogLevel.tiresomeDescription:
self = .tiresome
case NoosphereLogLevel.deafeningDescription:
self = .deafening
default:
self = .basic
}
}

var description: String {
switch self {
case .basic:
return NoosphereLogLevel.basicDescription
case .chatty:
return NoosphereLogLevel.chattyDescription
case .silent:
return NoosphereLogLevel.silentDescription
case .academic:
return NoosphereLogLevel.academicDescription
case .informed:
return NoosphereLogLevel.informedDescription
case .tiresome:
return NoosphereLogLevel.tiresomeDescription
case .deafening:
return NoosphereLogLevel.deafeningDescription
}
}

var rawValue: UInt32 {
switch self {
case .basic:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ actor NoosphereService:
self._sphere = nil
}

func setLogLevel(_ level: Noosphere.NoosphereLogLevel) {
logger.log("Set log level: \(level.description)")
self._noosphereLogLevel = level
self.reset()
}

/// Gets or creates memoized Noosphere singleton instance
private func noosphere() throws -> Noosphere {
if let noosphere = self._noosphere {
Expand Down
1 change: 1 addition & 0 deletions xcode/Subconscious/Shared/SubconsciousApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import OSLog
struct SubconsciousApp: App {
init() {
SentryIntegration.start()
NoosphereLogProxy.connect()
}

var body: some Scene {
Expand Down
8 changes: 8 additions & 0 deletions xcode/Subconscious/Subconscious.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
B58FD6752A4E4C8200826548 /* ResourceSyncBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58FD6742A4E4C8200826548 /* ResourceSyncBadge.swift */; };
B5908BEB29DAB05B00225B1A /* TestUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5908BEA29DAB05B00225B1A /* TestUtilities.swift */; };
B597C7372B4E34DB003F4342 /* ActivityEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B597C7362B4E34DA003F4342 /* ActivityEvent.swift */; };
B59850B52B328E6800FF8E65 /* NoosphereLogProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59850B42B328E6800FF8E65 /* NoosphereLogProxy.swift */; };
B59850B62B328E6800FF8E65 /* NoosphereLogProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59850B42B328E6800FF8E65 /* NoosphereLogProxy.swift */; };
B59D556329BBFF56007915E2 /* FormField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59D556229BBFF56007915E2 /* FormField.swift */; };
B59E4E572B3BA781000A2B49 /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59E4E562B3BA781000A2B49 /* CardView.swift */; };
B59E4E592B3BA8E7000A2B49 /* EntryCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59E4E582B3BA8E7000A2B49 /* EntryCardView.swift */; };
Expand Down Expand Up @@ -631,6 +633,7 @@
B58FD6742A4E4C8200826548 /* ResourceSyncBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceSyncBadge.swift; sourceTree = "<group>"; };
B5908BEA29DAB05B00225B1A /* TestUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUtilities.swift; sourceTree = "<group>"; };
B597C7362B4E34DA003F4342 /* ActivityEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityEvent.swift; sourceTree = "<group>"; };
B59850B42B328E6800FF8E65 /* NoosphereLogProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoosphereLogProxy.swift; sourceTree = "<group>"; };
B59D556229BBFF56007915E2 /* FormField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormField.swift; sourceTree = "<group>"; };
B59E4E562B3BA781000A2B49 /* CardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = "<group>"; };
B59E4E582B3BA8E7000A2B49 /* EntryCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryCardView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1677,6 +1680,9 @@
B8E00A2C299294A0003B40C1 /* UserDefaultsProperty.swift */,
B8B3EE7C297B1A1000779B7F /* ViewDebugUtilities.swift */,
B8249DA227E2753800BCDFBA /* ViewUtilities.swift */,
B542EF662AF48F2100BE29F1 /* StoreUtilities.swift */,
B56B80932B05AEA800AABF08 /* MathUtilities.swift */,
B59850B42B328E6800FF8E65 /* NoosphereLogProxy.swift */,
);
path = Library;
sourceTree = "<group>";
Expand Down Expand Up @@ -2255,6 +2261,7 @@
B59D556329BBFF56007915E2 /* FormField.swift in Sources */,
B5AD5C8E2AA6C37900FC5BC5 /* DetailStackView.swift in Sources */,
B513592A2AC2723E004385A7 /* GatewayURL.swift in Sources */,
B59850B52B328E6800FF8E65 /* NoosphereLogProxy.swift in Sources */,
B81A535C27275138001A6268 /* Tape.swift in Sources */,
B8B3EE7B297AFB9100779B7F /* TextFieldLabel.swift in Sources */,
B8B3194D2909E81D00A1E62A /* FileInfo.swift in Sources */,
Expand Down Expand Up @@ -2467,6 +2474,7 @@
B5D71D1A2A32B2AF000E058A /* NotFoundView.swift in Sources */,
B528CB3C2A5BB8C0001E3B8F /* FabSpacerView.swift in Sources */,
B8B6BCB629CCDDF6000DB410 /* ResourceStatus.swift in Sources */,
B59850B62B328E6800FF8E65 /* NoosphereLogProxy.swift in Sources */,
B58862C929F612CE006C2EE4 /* EditProfileSheet.swift in Sources */,
B84AD8EB2811C863006B3153 /* URLComponentsUtilities.swift in Sources */,
B8B4251328FDE7780081B8D5 /* Mapping.swift in Sources */,
Expand Down

0 comments on commit 21d3b08

Please sign in to comment.