-
Notifications
You must be signed in to change notification settings - Fork 2
feat(Periscope): add classified event authoring #273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c580175
c388ef0
d2c7858
cea0154
cfa3611
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import CompilerPluginSupport | ||
| import PackageDescription | ||
|
|
||
| let package = Package( | ||
| name: "Fixture", | ||
| dependencies: [ | ||
| .package(url: "https://example.com/fake-runtime", exact: "1.0.0"), | ||
| .package(url: "https://example.com/fake-syntax", exact: "1.0.0"), | ||
| ], | ||
| targets: [ | ||
| .target( | ||
| name: "Shipping", | ||
| dependencies: [ | ||
| .target(name: "FixtureMacro"), | ||
| .product(name: "FakeRuntime", package: "fake-runtime"), | ||
| ], | ||
| ), | ||
| .macro( | ||
| name: "FixtureMacro", | ||
| dependencies: [ | ||
| .product(name: "FakeSyntax", package: "fake-syntax"), | ||
| ], | ||
| ), | ||
| ], | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # frozen_string_literal: true | ||
|
|
||
| require "minitest/autorun" | ||
| require_relative "../generate-attribution" | ||
|
|
||
| class GenerateAttributionTest < Minitest::Test | ||
| FIXTURE = "Shared/CreditKit/Tools/Tests/Fixtures/MacroPackage.swift" | ||
|
|
||
| def test_macro_packages_are_linked_but_not_shipped | ||
| targets = package_targets(FIXTURE) | ||
| linked = targets.values.flat_map { |target| target["packages"] }.uniq | ||
| shipped = shipped_package_identities(targets, ["Shipping"]) | ||
|
|
||
| assert_equal %w[fake-runtime fake-syntax], linked | ||
| assert_equal ["fake-runtime"], shipped | ||
| assert_equal "macro", targets.fetch("FixtureMacro").fetch("kind") | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -111,51 +111,65 @@ extension [String: AmbientValue] { | |
| } | ||
| } | ||
|
|
||
| /// The standard event ambient sources emit: environmental context — | ||
| /// backgrounding, memory pressure, connectivity, thermal state — that helps | ||
| /// diagnose what the system was doing around an error. | ||
| public struct AmbientEvent: LogEvent, Hashable { | ||
| public static let eventName = "ambient" | ||
|
|
||
| /// Whether an event announces a lasting condition or a passing moment. | ||
| /// | ||
| /// Only `state` folds into the ``AmbientSnapshot`` every later record is | ||
| /// stamped with. A memory warning describes an instant, not a condition | ||
| /// the app stays in, so it must not stick to everything after it. | ||
| /// | ||
| /// The case names are the persisted wire values — renaming one rewrites | ||
| /// the format for stored rows. | ||
| public enum Reporting: String, Hashable, Sendable, Codable { | ||
| /// A lasting condition: the newest value replaces the previous one | ||
| /// and describes the app until it changes again. | ||
| case state | ||
| /// A momentary occurrence, meaningful only at its own timestamp. | ||
| case occurrence | ||
| } | ||
|
|
||
| public var kind: AmbientKind | ||
| /// The state as named fields (`["level": "serious"]`, | ||
| /// `["voiceover": false]`) — a JSON object in the payload, not a | ||
| /// formatted sentence the tooling would have to parse back apart. | ||
| public var value: [String: AmbientValue] | ||
| public var level: LogLevel | ||
| /// Defaults to ``Reporting/state`` — "ambient" means a surrounding | ||
| /// condition, and a source that reports moments is the exception. | ||
| public var reporting: Reporting | ||
|
|
||
| public var message: String { | ||
| "\(kind): \(value.ambientDescription)" | ||
| } | ||
| /// The built-in scope for environmental state and occurrence events. | ||
| @LogScope("ambient") | ||
| public enum AmbientLog { | ||
| /// The standard event ambient sources emit: environmental context — | ||
| /// backgrounding, memory pressure, connectivity, thermal state — that helps | ||
| /// diagnose what the system was doing around an error. | ||
| @LogEvent("event") | ||
| public struct Event: Hashable { | ||
| /// Whether an event announces a lasting condition or a passing moment. | ||
| /// | ||
| /// Only `state` folds into the ``AmbientSnapshot`` every later record is | ||
| /// stamped with. A memory warning describes an instant, not a condition | ||
| /// the app stays in, so it must not stick to everything after it. | ||
| /// | ||
| /// The case names are the persisted wire values — renaming one rewrites | ||
| /// the format for stored rows. | ||
| public enum Reporting: String, Hashable, Sendable, Codable { | ||
| /// A lasting condition: the newest value replaces the previous one | ||
| /// and describes the app until it changes again. | ||
| case state | ||
| /// A momentary occurrence, meaningful only at its own timestamp. | ||
| case occurrence | ||
| } | ||
|
|
||
| public init( | ||
| kind: AmbientKind, | ||
| value: [String: AmbientValue], | ||
| level: LogLevel = .info, | ||
| reporting: Reporting = .state, | ||
| ) { | ||
| self.kind = kind | ||
| self.value = value | ||
| self.level = level | ||
| self.reporting = reporting | ||
| @LogField( | ||
| "kind", | ||
| exposure: .restricted, | ||
| kind: .technicalState, | ||
| ) | ||
| public var kind: AmbientKind | ||
|
|
||
| /// The state as named fields (`["level": "serious"]`, | ||
| /// `["voiceover": false]`) — a JSON object in the payload, not a | ||
| /// formatted sentence the tooling would have to parse back apart. | ||
| @LogField( | ||
| "value", | ||
| exposure: .restricted, | ||
| kind: .domainValue, | ||
| ) | ||
| public var value: [String: AmbientValue] | ||
|
|
||
| @LogField( | ||
| "level", | ||
| exposure: .restricted, | ||
| kind: .technicalState, | ||
| ) | ||
| public var level: LogLevel | ||
|
|
||
| @LogField( | ||
| "reporting", | ||
| exposure: .restricted, | ||
| kind: .technicalState, | ||
| ) | ||
| public var reporting: AmbientLog.Event.Reporting | ||
|
|
||
| public var message: String { | ||
| "\(kind): \(value.ambientDescription)" | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public typealias AmbientEvent = AmbientLog.Event | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO not useful, let's remove and just use the full type qualification. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,12 @@ | |
|
|
||
| override public func event(for notification: Notification) -> AmbientEvent? { | ||
| Self.values[notification.name].map { | ||
| AmbientEvent(kind: .appLifecycle, value: ["phase": .string($0)]) | ||
| AmbientEvent( | ||
| kind: .restricted(.technicalState, .appLifecycle), | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this should be restricted, it's not PII or otherwise identifiable? |
||
| value: .restricted(.domainValue, ["phase": .string($0)]), | ||
| level: .restricted(.technicalState, .info), | ||
| reporting: .restricted(.technicalState, .state), | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ | |
| /// Logs Low Power Mode at start and on every transition — background work | ||
| /// behaves differently under it, which matters when diagnosing "it only | ||
| /// breaks sometimes". | ||
| public final class LowPowerModeAmbientSource: NotificationAmbientSource { | ||
|
Check warning on line 6 in Shared/Periscope/PeriscopeCore/Sources/Ambient/LowPowerModeAmbientSource.swift
|
||
| override public var observedNames: [Notification.Name] { | ||
| [.NSProcessInfoPowerStateDidChange] | ||
| } | ||
|
|
@@ -22,6 +22,11 @@ | |
|
|
||
| private static func currentEvent() -> AmbientEvent { | ||
| let enabled = ProcessInfo.processInfo.isLowPowerModeEnabled | ||
| return AmbientEvent(kind: .powerMode, value: ["low-power": .bool(enabled)]) | ||
| return AmbientEvent( | ||
| kind: .restricted(.technicalState, .powerMode), | ||
| value: .restricted(.domainValue, ["low-power": .bool(enabled)]), | ||
| level: .restricted(.technicalState, .info), | ||
| reporting: .restricted(.technicalState, .state), | ||
| ) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ | |
| /// Logs the thermal state at start and on every change; `serious` and | ||
| /// `critical` log at `.warning` since the system is about to start | ||
| /// throttling. | ||
| public final class ThermalStateAmbientSource: NotificationAmbientSource { | ||
|
Check warning on line 6 in Shared/Periscope/PeriscopeCore/Sources/Ambient/ThermalStateAmbientSource.swift
|
||
| override public var observedNames: [Notification.Name] { | ||
| [ProcessInfo.thermalStateDidChangeNotification] | ||
| } | ||
|
|
@@ -43,9 +43,10 @@ | |
| logLevel = .info | ||
| } | ||
| return AmbientEvent( | ||
| kind: .thermalState, | ||
| value: ["level": .string(level)], | ||
| level: logLevel, | ||
| kind: .restricted(.technicalState, .thermalState), | ||
| value: .restricted(.domainValue, ["level": .string(level)]), | ||
| level: .restricted(.technicalState, logLevel), | ||
| reporting: .restricted(.technicalState, .state), | ||
| ) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(important) Is this useful to keep at all if the macro gives us access to the JSON and we can format it on ask?