feat: add swift-log app logging controls
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
- UI: add optional `ui.seamColor` accent to tint the Talk Mode side bubble (macOS/iOS/Android).
|
- UI: add optional `ui.seamColor` accent to tint the Talk Mode side bubble (macOS/iOS/Android).
|
||||||
- Agent runtime: accept legacy `Z_AI_API_KEY` for Z.AI provider auth (maps to `ZAI_API_KEY`).
|
- Agent runtime: accept legacy `Z_AI_API_KEY` for Z.AI provider auth (maps to `ZAI_API_KEY`).
|
||||||
- Tests: add a Z.AI live test gate for smoke validation when keys are present.
|
- Tests: add a Z.AI live test gate for smoke validation when keys are present.
|
||||||
|
- macOS Debug: add app log verbosity and rolling file log toggle for swift-log-backed app logs.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Docs/agent tools: clarify that browser `wait` should be avoided by default and used only in exceptional cases.
|
- Docs/agent tools: clarify that browser `wait` should be avoided by default and used only in exceptional cases.
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ let package = Package(
|
|||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/orchetect/MenuBarExtraAccess", exact: "1.2.2"),
|
.package(url: "https://github.com/orchetect/MenuBarExtraAccess", exact: "1.2.2"),
|
||||||
.package(url: "https://github.com/swiftlang/swift-subprocess.git", from: "0.1.0"),
|
.package(url: "https://github.com/swiftlang/swift-subprocess.git", from: "0.1.0"),
|
||||||
|
.package(url: "https://github.com/apple/swift-log.git", from: "1.8.0"),
|
||||||
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.8.1"),
|
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.8.1"),
|
||||||
.package(path: "../shared/ClawdisKit"),
|
.package(path: "../shared/ClawdisKit"),
|
||||||
.package(path: "../../Swabble"),
|
.package(path: "../../Swabble"),
|
||||||
@@ -45,6 +46,7 @@ let package = Package(
|
|||||||
.product(name: "SwabbleKit", package: "swabble"),
|
.product(name: "SwabbleKit", package: "swabble"),
|
||||||
.product(name: "MenuBarExtraAccess", package: "MenuBarExtraAccess"),
|
.product(name: "MenuBarExtraAccess", package: "MenuBarExtraAccess"),
|
||||||
.product(name: "Subprocess", package: "swift-subprocess"),
|
.product(name: "Subprocess", package: "swift-subprocess"),
|
||||||
|
.product(name: "Logging", package: "swift-log"),
|
||||||
.product(name: "Sparkle", package: "Sparkle"),
|
.product(name: "Sparkle", package: "Sparkle"),
|
||||||
.product(name: "PeekabooBridge", package: "PeekabooCore"),
|
.product(name: "PeekabooBridge", package: "PeekabooCore"),
|
||||||
.product(name: "PeekabooAutomationKit", package: "PeekabooAutomationKit"),
|
.product(name: "PeekabooAutomationKit", package: "PeekabooAutomationKit"),
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import AppKit
|
import AppKit
|
||||||
import OSLog
|
|
||||||
|
|
||||||
let canvasWindowLogger = Logger(subsystem: "com.steipete.clawdis", category: "Canvas")
|
let canvasWindowLogger = Logger(subsystem: "com.steipete.clawdis", category: "Canvas")
|
||||||
|
|
||||||
|
|||||||
@@ -32,5 +32,6 @@ let modelCatalogReloadKey = "clawdis.modelCatalogReload"
|
|||||||
let attachExistingGatewayOnlyKey = "clawdis.gateway.attachExistingOnly"
|
let attachExistingGatewayOnlyKey = "clawdis.gateway.attachExistingOnly"
|
||||||
let heartbeatsEnabledKey = "clawdis.heartbeatsEnabled"
|
let heartbeatsEnabledKey = "clawdis.heartbeatsEnabled"
|
||||||
let debugFileLogEnabledKey = "clawdis.debug.fileLogEnabled"
|
let debugFileLogEnabledKey = "clawdis.debug.fileLogEnabled"
|
||||||
|
let appLogLevelKey = "clawdis.debug.appLogLevel"
|
||||||
let voiceWakeSupported: Bool = ProcessInfo.processInfo.operatingSystemVersion.majorVersion >= 26
|
let voiceWakeSupported: Bool = ProcessInfo.processInfo.operatingSystemVersion.majorVersion >= 26
|
||||||
let cliHelperSearchPaths = ["/usr/local/bin", "/opt/homebrew/bin"]
|
let cliHelperSearchPaths = ["/usr/local/bin", "/opt/homebrew/bin"]
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import ClawdisProtocol
|
import ClawdisProtocol
|
||||||
import Foundation
|
import Foundation
|
||||||
import Observation
|
import Observation
|
||||||
import OSLog
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ControlHeartbeatEvent: Codable {
|
struct ControlHeartbeatEvent: Codable {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ struct DebugSettings: View {
|
|||||||
@State private var pendingKill: DebugActions.PortListener?
|
@State private var pendingKill: DebugActions.PortListener?
|
||||||
@AppStorage(attachExistingGatewayOnlyKey) private var attachExistingGatewayOnly: Bool = false
|
@AppStorage(attachExistingGatewayOnlyKey) private var attachExistingGatewayOnly: Bool = false
|
||||||
@AppStorage(debugFileLogEnabledKey) private var diagnosticsFileLogEnabled: Bool = false
|
@AppStorage(debugFileLogEnabledKey) private var diagnosticsFileLogEnabled: Bool = false
|
||||||
|
@AppStorage(appLogLevelKey) private var appLogLevelRaw: String = AppLogLevel.default.rawValue
|
||||||
|
|
||||||
@State private var canvasSessionKey: String = "main"
|
@State private var canvasSessionKey: String = "main"
|
||||||
@State private var canvasStatus: String?
|
@State private var canvasStatus: String?
|
||||||
@@ -232,13 +233,23 @@ struct DebugSettings: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GridRow {
|
GridRow {
|
||||||
self.gridLabel("Diagnostics")
|
self.gridLabel("App logging")
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
Picker("Verbosity", selection: self.$appLogLevelRaw) {
|
||||||
|
ForEach(AppLogLevel.allCases) { level in
|
||||||
|
Text(level.title).tag(level.rawValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.menu)
|
||||||
|
.labelsHidden()
|
||||||
|
.help("Controls the macOS app log verbosity.")
|
||||||
|
|
||||||
Toggle("Write rolling diagnostics log (JSONL)", isOn: self.$diagnosticsFileLogEnabled)
|
Toggle("Write rolling diagnostics log (JSONL)", isOn: self.$diagnosticsFileLogEnabled)
|
||||||
.toggleStyle(.checkbox)
|
.toggleStyle(.checkbox)
|
||||||
.help(
|
.help(
|
||||||
"Writes a rotating, local-only diagnostics log under ~/Library/Logs/Clawdis/. " +
|
"Writes a rotating, local-only log under ~/Library/Logs/Clawdis/. " +
|
||||||
"Enable only while actively debugging.")
|
"Enable only while actively debugging.")
|
||||||
|
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
Button("Open folder") {
|
Button("Open folder") {
|
||||||
NSWorkspace.shared.open(DiagnosticsFileLog.logDirectoryURL())
|
NSWorkspace.shared.open(DiagnosticsFileLog.logDirectoryURL())
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import AppKit
|
import AppKit
|
||||||
import OSLog
|
|
||||||
|
|
||||||
/// Central manager for Dock icon visibility.
|
/// Central manager for Dock icon visibility.
|
||||||
/// Shows the Dock icon while any windows are visible, regardless of user preference.
|
/// Shows the Dock icon while any windows are visible, regardless of user preference.
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Network
|
import Network
|
||||||
import Observation
|
import Observation
|
||||||
import OSLog
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct HealthSnapshot: Codable, Sendable {
|
struct HealthSnapshot: Codable, Sendable {
|
||||||
|
|||||||
229
apps/macos/Sources/Clawdis/Logging/ClawdisLogging.swift
Normal file
229
apps/macos/Sources/Clawdis/Logging/ClawdisLogging.swift
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
@_exported import Logging
|
||||||
|
import Foundation
|
||||||
|
import OSLog
|
||||||
|
|
||||||
|
typealias Logger = Logging.Logger
|
||||||
|
|
||||||
|
enum AppLogSettings {
|
||||||
|
static let logLevelKey = appLogLevelKey
|
||||||
|
|
||||||
|
static func logLevel() -> Logger.Level {
|
||||||
|
if let raw = UserDefaults.standard.string(forKey: self.logLevelKey),
|
||||||
|
let level = Logger.Level(rawValue: raw)
|
||||||
|
{
|
||||||
|
return level
|
||||||
|
}
|
||||||
|
return .info
|
||||||
|
}
|
||||||
|
|
||||||
|
static func setLogLevel(_ level: Logger.Level) {
|
||||||
|
UserDefaults.standard.set(level.rawValue, forKey: self.logLevelKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func fileLoggingEnabled() -> Bool {
|
||||||
|
UserDefaults.standard.bool(forKey: debugFileLogEnabledKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AppLogLevel: String, CaseIterable, Identifiable {
|
||||||
|
case trace
|
||||||
|
case debug
|
||||||
|
case info
|
||||||
|
case notice
|
||||||
|
case warning
|
||||||
|
case error
|
||||||
|
case critical
|
||||||
|
|
||||||
|
static let `default`: AppLogLevel = .info
|
||||||
|
|
||||||
|
var id: String { self.rawValue }
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
switch self {
|
||||||
|
case .trace: "Trace"
|
||||||
|
case .debug: "Debug"
|
||||||
|
case .info: "Info"
|
||||||
|
case .notice: "Notice"
|
||||||
|
case .warning: "Warning"
|
||||||
|
case .error: "Error"
|
||||||
|
case .critical: "Critical"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ClawdisLogging {
|
||||||
|
private static let labelSeparator = "::"
|
||||||
|
|
||||||
|
private static let didBootstrap: Void = {
|
||||||
|
LoggingSystem.bootstrap { label in
|
||||||
|
let (subsystem, category) = Self.parseLabel(label)
|
||||||
|
let osHandler = ClawdisOSLogHandler(subsystem: subsystem, category: category)
|
||||||
|
let fileHandler = ClawdisFileLogHandler(label: label)
|
||||||
|
return MultiplexLogHandler([osHandler, fileHandler])
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
static func bootstrapIfNeeded() {
|
||||||
|
_ = Self.didBootstrap
|
||||||
|
}
|
||||||
|
|
||||||
|
static func makeLabel(subsystem: String, category: String) -> String {
|
||||||
|
"\(subsystem)\(Self.labelSeparator)\(category)"
|
||||||
|
}
|
||||||
|
|
||||||
|
static func parseLabel(_ label: String) -> (String, String) {
|
||||||
|
guard let range = label.range(of: Self.labelSeparator) else {
|
||||||
|
return ("com.steipete.clawdis", label)
|
||||||
|
}
|
||||||
|
let subsystem = String(label[..<range.lowerBound])
|
||||||
|
let category = String(label[range.upperBound...])
|
||||||
|
return (subsystem, category)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Logging.Logger {
|
||||||
|
init(subsystem: String, category: String) {
|
||||||
|
ClawdisLogging.bootstrapIfNeeded()
|
||||||
|
let label = ClawdisLogging.makeLabel(subsystem: subsystem, category: category)
|
||||||
|
self.init(label: label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Logger.Message.StringInterpolation {
|
||||||
|
mutating func appendInterpolation<T>(_ value: T, privacy: OSLogPrivacy) {
|
||||||
|
self.appendInterpolation(String(describing: value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ClawdisOSLogHandler: LogHandler {
|
||||||
|
private let osLogger: OSLog.Logger
|
||||||
|
var metadata: Logger.Metadata = [:]
|
||||||
|
|
||||||
|
var logLevel: Logger.Level {
|
||||||
|
get { AppLogSettings.logLevel() }
|
||||||
|
set { AppLogSettings.setLogLevel(newValue) }
|
||||||
|
}
|
||||||
|
|
||||||
|
init(subsystem: String, category: String) {
|
||||||
|
self.osLogger = OSLog.Logger(subsystem: subsystem, category: category)
|
||||||
|
}
|
||||||
|
|
||||||
|
subscript(metadataKey key: String) -> Logger.Metadata.Value? {
|
||||||
|
get { self.metadata[key] }
|
||||||
|
set { self.metadata[key] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
func log(
|
||||||
|
level: Logger.Level,
|
||||||
|
message: Logger.Message,
|
||||||
|
metadata: Logger.Metadata?,
|
||||||
|
source: String,
|
||||||
|
file: String,
|
||||||
|
function: String,
|
||||||
|
line: UInt)
|
||||||
|
{
|
||||||
|
let merged = Self.mergeMetadata(self.metadata, metadata)
|
||||||
|
let rendered = Self.renderMessage(message, metadata: merged)
|
||||||
|
self.osLogger.log(level: Self.osLogType(for: level), "\(rendered, privacy: .public)")
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func osLogType(for level: Logger.Level) -> OSLogType {
|
||||||
|
switch level {
|
||||||
|
case .trace, .debug:
|
||||||
|
return .debug
|
||||||
|
case .info, .notice:
|
||||||
|
return .info
|
||||||
|
case .warning:
|
||||||
|
return .default
|
||||||
|
case .error:
|
||||||
|
return .error
|
||||||
|
case .critical:
|
||||||
|
return .fault
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func mergeMetadata(
|
||||||
|
_ base: Logger.Metadata,
|
||||||
|
_ extra: Logger.Metadata?) -> Logger.Metadata
|
||||||
|
{
|
||||||
|
guard let extra else { return base }
|
||||||
|
return base.merging(extra, uniquingKeysWith: { _, new in new })
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func renderMessage(_ message: Logger.Message, metadata: Logger.Metadata) -> String {
|
||||||
|
guard !metadata.isEmpty else { return message.description }
|
||||||
|
let meta = metadata
|
||||||
|
.sorted(by: { $0.key < $1.key })
|
||||||
|
.map { "\($0.key)=\(stringify($0.value))" }
|
||||||
|
.joined(separator: " ")
|
||||||
|
return "\(message.description) [\(meta)]"
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func stringify(_ value: Logger.Metadata.Value) -> String {
|
||||||
|
switch value {
|
||||||
|
case let .string(text):
|
||||||
|
text
|
||||||
|
case let .stringConvertible(value):
|
||||||
|
String(describing: value)
|
||||||
|
case let .array(values):
|
||||||
|
"[" + values.map { stringify($0) }.joined(separator: ",") + "]"
|
||||||
|
case let .dictionary(entries):
|
||||||
|
"{" + entries.map { "\($0.key)=\(stringify($0.value))" }.joined(separator: ",") + "}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ClawdisFileLogHandler: LogHandler {
|
||||||
|
let label: String
|
||||||
|
var metadata: Logger.Metadata = [:]
|
||||||
|
|
||||||
|
var logLevel: Logger.Level {
|
||||||
|
get { AppLogSettings.logLevel() }
|
||||||
|
set { AppLogSettings.setLogLevel(newValue) }
|
||||||
|
}
|
||||||
|
|
||||||
|
subscript(metadataKey key: String) -> Logger.Metadata.Value? {
|
||||||
|
get { self.metadata[key] }
|
||||||
|
set { self.metadata[key] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
func log(
|
||||||
|
level: Logger.Level,
|
||||||
|
message: Logger.Message,
|
||||||
|
metadata: Logger.Metadata?,
|
||||||
|
source: String,
|
||||||
|
file: String,
|
||||||
|
function: String,
|
||||||
|
line: UInt)
|
||||||
|
{
|
||||||
|
guard AppLogSettings.fileLoggingEnabled() else { return }
|
||||||
|
let (subsystem, category) = ClawdisLogging.parseLabel(self.label)
|
||||||
|
var fields: [String: String] = [
|
||||||
|
"subsystem": subsystem,
|
||||||
|
"category": category,
|
||||||
|
"level": level.rawValue,
|
||||||
|
"source": source,
|
||||||
|
"file": file,
|
||||||
|
"function": function,
|
||||||
|
"line": "\(line)",
|
||||||
|
]
|
||||||
|
let merged = self.metadata.merging(metadata ?? [:], uniquingKeysWith: { _, new in new })
|
||||||
|
for (key, value) in merged {
|
||||||
|
fields["meta.\(key)"] = Self.stringify(value)
|
||||||
|
}
|
||||||
|
DiagnosticsFileLog.shared.log(category: category, event: message.description, fields: fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func stringify(_ value: Logger.Metadata.Value) -> String {
|
||||||
|
switch value {
|
||||||
|
case let .string(text):
|
||||||
|
text
|
||||||
|
case let .stringConvertible(value):
|
||||||
|
String(describing: value)
|
||||||
|
case let .array(values):
|
||||||
|
"[" + values.map { stringify($0) }.joined(separator: ",") + "]"
|
||||||
|
case let .dictionary(entries):
|
||||||
|
"{" + entries.map { "\($0.key)=\(stringify($0.value))" }.joined(separator: ",") + "}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ import Darwin
|
|||||||
import Foundation
|
import Foundation
|
||||||
import MenuBarExtraAccess
|
import MenuBarExtraAccess
|
||||||
import Observation
|
import Observation
|
||||||
import OSLog
|
|
||||||
import Security
|
import Security
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@@ -30,6 +29,7 @@ struct ClawdisApp: App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
ClawdisLogging.bootstrapIfNeeded()
|
||||||
_state = State(initialValue: AppStateStore.shared)
|
_state = State(initialValue: AppStateStore.shared)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ struct MenuContent: View {
|
|||||||
@State private var loadingMics = false
|
@State private var loadingMics = false
|
||||||
@State private var browserControlEnabled = true
|
@State private var browserControlEnabled = true
|
||||||
@AppStorage(cameraEnabledKey) private var cameraEnabled: Bool = false
|
@AppStorage(cameraEnabledKey) private var cameraEnabled: Bool = false
|
||||||
|
@AppStorage(appLogLevelKey) private var appLogLevelRaw: String = AppLogLevel.default.rawValue
|
||||||
|
@AppStorage(debugFileLogEnabledKey) private var appFileLoggingEnabled: Bool = false
|
||||||
|
|
||||||
init(state: AppState, updater: UpdaterProviding?) {
|
init(state: AppState, updater: UpdaterProviding?) {
|
||||||
self._state = Bindable(wrappedValue: state)
|
self._state = Bindable(wrappedValue: state)
|
||||||
@@ -182,6 +184,20 @@ struct MenuContent: View {
|
|||||||
: "Verbose Logging (Main): Off",
|
: "Verbose Logging (Main): Off",
|
||||||
systemImage: "text.alignleft")
|
systemImage: "text.alignleft")
|
||||||
}
|
}
|
||||||
|
Menu("App Logging") {
|
||||||
|
Picker("Verbosity", selection: self.$appLogLevelRaw) {
|
||||||
|
ForEach(AppLogLevel.allCases) { level in
|
||||||
|
Text(level.title).tag(level.rawValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Toggle(isOn: self.$appFileLoggingEnabled) {
|
||||||
|
Label(
|
||||||
|
self.appFileLoggingEnabled
|
||||||
|
? "File Logging: On"
|
||||||
|
: "File Logging: Off",
|
||||||
|
systemImage: "doc.text.magnifyingglass")
|
||||||
|
}
|
||||||
|
}
|
||||||
Button {
|
Button {
|
||||||
DebugActions.openSessionStore()
|
DebugActions.openSessionStore()
|
||||||
} label: {
|
} label: {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import ClawdisIPC
|
|||||||
import CoreGraphics
|
import CoreGraphics
|
||||||
import Foundation
|
import Foundation
|
||||||
import Observation
|
import Observation
|
||||||
import OSLog
|
|
||||||
import Speech
|
import Speech
|
||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import Observation
|
import Observation
|
||||||
import OSLog
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Observable
|
@Observable
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import AppKit
|
import AppKit
|
||||||
import Foundation
|
import Foundation
|
||||||
import Observation
|
import Observation
|
||||||
import OSLog
|
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Observable
|
@Observable
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import AppKit
|
import AppKit
|
||||||
import Observation
|
import Observation
|
||||||
import OSLog
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
/// Lightweight, borderless panel that shows the current voice wake transcript near the menu bar.
|
/// Lightweight, borderless panel that shows the current voice wake transcript near the menu bar.
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import AVFoundation
|
import AVFoundation
|
||||||
import Foundation
|
import Foundation
|
||||||
import OSLog
|
|
||||||
import Speech
|
import Speech
|
||||||
import SwabbleKit
|
import SwabbleKit
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ read_when:
|
|||||||
# Logging (macOS)
|
# Logging (macOS)
|
||||||
|
|
||||||
## Rolling diagnostics file log (Debug pane)
|
## Rolling diagnostics file log (Debug pane)
|
||||||
Clawdis can write a local, rotating diagnostics log to disk (useful when macOS unified logging is impractical during iterative repros).
|
Clawdis routes macOS app logs through swift-log (unified logging by default) and can write a local, rotating file log to disk when you need a durable capture.
|
||||||
|
|
||||||
- Enable: **Debug pane → Diagnostics log → “Write rolling diagnostics log (JSONL)”**
|
- Verbosity: **Debug pane → Logs → App logging → Verbosity**
|
||||||
|
- Enable: **Debug pane → Logs → App logging → “Write rolling diagnostics log (JSONL)”**
|
||||||
- Location: `~/Library/Logs/Clawdis/diagnostics.jsonl` (rotates automatically; old files are suffixed with `.1`, `.2`, …)
|
- Location: `~/Library/Logs/Clawdis/diagnostics.jsonl` (rotates automatically; old files are suffixed with `.1`, `.2`, …)
|
||||||
- Clear: **Debug pane → Diagnostics log → “Clear”**
|
- Clear: **Debug pane → Logs → App logging → “Clear”**
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- This is **off by default**. Enable only while actively debugging.
|
- This is **off by default**. Enable only while actively debugging.
|
||||||
|
|||||||
Reference in New Issue
Block a user