From 161c549ffd46bf0e13fd335952a24fd70ee5c40e Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 30 Aug 2023 15:35:49 +0300 Subject: [PATCH] Allow setting custom log levels (#1592) * Allow using custom tracing configuration from the developer options screen. Clean up RustTracing * Move log level configurations to a separate view * Disable autocorrection * Fix unit tests * Use TracingConfiguration.info as the default text value, switch to a TextEditor --- ElementX/Sources/Other/Logging/MXLog.swift | 2 +- .../Sources/Other/Logging/RustTracing.swift | 79 +++++++++++++------ .../View/DeveloperOptionsScreen.swift | 56 ++++++++++--- .../Sources/TracingConfigurationTests.swift | 16 ++-- 4 files changed, 109 insertions(+), 44 deletions(-) diff --git a/ElementX/Sources/Other/Logging/MXLog.swift b/ElementX/Sources/Other/Logging/MXLog.swift index 96178dda0..6b0e236d5 100644 --- a/ElementX/Sources/Other/Logging/MXLog.swift +++ b/ElementX/Sources/Other/Logging/MXLog.swift @@ -59,7 +59,7 @@ enum MXLog { return } - setupTracing(configuration: .custom(logLevel: logLevel), otlpConfiguration: otlpConfiguration) + setupTracing(configuration: .init(logLevel: logLevel), otlpConfiguration: otlpConfiguration) if let target { self.target = target diff --git a/ElementX/Sources/Other/Logging/RustTracing.swift b/ElementX/Sources/Other/Logging/RustTracing.swift index 0e65e80e0..aa8f0c99a 100644 --- a/ElementX/Sources/Other/Logging/RustTracing.swift +++ b/ElementX/Sources/Other/Logging/RustTracing.swift @@ -27,31 +27,45 @@ struct OTLPConfiguration { // We can filter by level, crate and even file. See more details here: // https://docs.rs/tracing-subscriber/0.2.7/tracing_subscriber/filter/struct.EnvFilter.html#examples struct TracingConfiguration { - /// Configure tracing with certain overrides in place - /// - Parameter overrides: the desired overrides - /// - Returns: a custom tracing configuration - static func custom(overrides: [Target: LogLevel]) -> TracingConfiguration { - TracingConfiguration(overrides: overrides) - } - - /// Sets the same log level for all Targets - /// - Parameter logLevel: the desired log level - /// - Returns: a custom tracing configuration - static func custom(logLevel: LogLevel) -> TracingConfiguration { - let overrides = targets.keys.reduce(into: [Target: LogLevel]()) { partialResult, target in - // Keep the defaults here - if target == .common || target == .hyper { - return + enum LogLevel: Codable, Hashable { + case error, warn, info, debug, trace + case custom(String) + + var title: String { + switch self { + case .error: + return "Error" + case .warn: + return "Warning" + case .info: + return "Info" + case .debug: + return "Debug" + case .trace: + return "Trace" + case .custom: + return "Custom" + } + } + + fileprivate var rawValue: String { + switch self { + case .error: + return "error" + case .warn: + return "warn" + case .info: + return "info" + case .debug: + return "debug" + case .trace: + return "trace" + case .custom(let filter): + return filter } - - partialResult[target] = logLevel } - - return TracingConfiguration(overrides: overrides) } - enum LogLevel: String, Codable, CaseIterable { case error, warn, info, debug, trace } - enum Target: String { case common = "" @@ -77,9 +91,26 @@ struct TracingConfiguration { .matrix_sdk_ui_timeline: .info ] - var overrides = [Target: LogLevel]() + let filter: String - var filter: String { + /// Sets the same log level for all Targets + /// - Parameter logLevel: the desired log level + /// - Returns: a custom tracing configuration + init(logLevel: LogLevel) { + if case let .custom(filter) = logLevel { + self.filter = filter + return + } + + let overrides = Self.targets.keys.reduce(into: [Target: LogLevel]()) { partialResult, target in + // Keep the defaults here + if target == .common || target == .hyper { + return + } + + partialResult[target] = logLevel + } + var newTargets = Self.targets for (target, logLevel) in overrides { newTargets.updateValue(logLevel, forKey: target) @@ -93,7 +124,7 @@ struct TracingConfiguration { return "\(target.rawValue)=\(logLevel.rawValue)" } - return components.joined(separator: ",") + filter = components.joined(separator: ",") } } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index 1d5e5d753..d9556bfb7 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -19,18 +19,11 @@ import SwiftUI struct DeveloperOptionsScreen: View { @ObservedObject var context: DeveloperOptionsScreenViewModel.Context @State private var showConfetti = false - + var body: some View { Form { Section("Logging") { - Picker(selection: $context.logLevel) { - ForEach(TracingConfiguration.LogLevel.allCases, id: \.self) { logLevel in - Text(logLevel.rawValue.capitalized) - } - } label: { - Text("Log level") - Text("Requires app reboot") - } + LogLevelConfigurationView(logLevel: $context.logLevel) Toggle(isOn: $context.otlpTracingEnabled) { Text("OTLP tracing") @@ -130,6 +123,51 @@ struct DeveloperOptionsScreen: View { } } +private struct LogLevelConfigurationView: View { + @Binding var logLevel: TracingConfiguration.LogLevel + + @State private var customTracingConfiguration: String + + init(logLevel: Binding) { + _logLevel = logLevel + + if case .custom(let configuration) = logLevel.wrappedValue { + customTracingConfiguration = configuration + } else { + customTracingConfiguration = TracingConfiguration(logLevel: .info).filter + } + } + + var body: some View { + Picker(selection: $logLevel) { + ForEach(logLevels, id: \.self) { logLevel in + Text(logLevel.title) + } + } label: { + Text("Log level") + Text("Requires app reboot") + } + + if case .custom = logLevel { + TextEditor(text: $customTracingConfiguration) + .textInputAutocapitalization(.never) + .autocorrectionDisabled() + .onChange(of: customTracingConfiguration) { newValue in + logLevel = .custom(newValue) + } + } + } + + /// Allows the picker to work with associated values + private var logLevels: [TracingConfiguration.LogLevel] { + if case let .custom(filter) = logLevel { + return [.error, .warn, .info, .debug, .trace, .custom(filter)] + } else { + return [.error, .warn, .info, .debug, .trace, .custom("")] + } + } +} + // MARK: - Previews struct DeveloperOptionsScreen_Previews: PreviewProvider { diff --git a/UnitTests/Sources/TracingConfigurationTests.swift b/UnitTests/Sources/TracingConfigurationTests.swift index 375f4cea6..43c6a81e5 100644 --- a/UnitTests/Sources/TracingConfigurationTests.swift +++ b/UnitTests/Sources/TracingConfigurationTests.swift @@ -20,17 +20,13 @@ import XCTest class TracingConfigurationTests: XCTestCase { func testConfiguration() { - let configuration = TracingConfiguration(overrides: [.common: .trace, - .matrix_sdk_base_sliding_sync: .error, - .matrix_sdk_http_client: .warn, - .matrix_sdk_crypto: .info, - .hyper: .debug]) + let configuration = TracingConfiguration(logLevel: .trace) let filterComponents = configuration.filter.components(separatedBy: ",") - XCTAssertEqual(filterComponents.first, "trace") - XCTAssertTrue(filterComponents.contains("matrix_sdk_base::sliding_sync=error")) - XCTAssertTrue(filterComponents.contains("matrix_sdk::http_client=warn")) - XCTAssertTrue(filterComponents.contains("matrix_sdk_crypto=info")) - XCTAssertTrue(filterComponents.contains("hyper=debug")) + XCTAssertEqual(filterComponents.first, "info") + XCTAssertTrue(filterComponents.contains("matrix_sdk_base::sliding_sync=trace")) + XCTAssertTrue(filterComponents.contains("matrix_sdk::http_client=trace")) + XCTAssertTrue(filterComponents.contains("matrix_sdk_crypto=trace")) + XCTAssertTrue(filterComponents.contains("hyper=warn")) } }