Swiftpack.co - grype/SwiftBeacon as Swift Package

Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
See all packages published by grype.
grype/SwiftBeacon 3.0.1
Structured logging in Swift and Objective-C
⭐️ 2
🕓 1 year ago
iOS macOS watchOS tvOS
.package(url: "https://github.com/grype/SwiftBeacon.git", from: "3.0.1")

Beacon

CI SPM

Structured logging in Swift and Objective-C

Beacon distinguishes itself from conventional logging systems by working with arbitrary values, not just strings, and doing away with severity levels in favor of type-based filtering.

This framework provides all the essentials for logging any type of value to the console, a file or a remote service. It also provides a broader support for buffered and stream-based logging, making it easy to implement custom logging facilities.

For more information:

Using

Logging with Beacon is a matter of starting a logger and emitting a value.

let consoleLogger = ConsoleLogger.starting(name: "Console")
emit("A message")

The above is equivalent to printing a time-stamped message to the console, à la conventional logging systems. You are not, however, limited to emitting strings - any type of value can be emitted:

do { 
	let result: MyThing = try something()
	emit(result)
} catch { 
	emit(error: error) 
}

There is no need to specify debug levels - simply emit a value of interest. Beacon lets you control what gets logged and where by means of filters and constraints.

In a nutshell...

⓪ let memoryLogger = MemoryLogger(name: "Memory")

① let consoleLogger = ConsoleLogger.starting(name: "Console") { 
②	$0 is StringSignal 
   }

③ Constraint.activate {
	-Signal.self
	+ErrorSignal.self ~> memoryLogger
   }

④ emit()

  do {
⑤	memoryLogger.run {
		let result = try something()
⑥		emit(result, on: [.shared], userInfo: ["detail": "Detail info"])
	 }
  }
  catch {
⑦	emit(error: error)
  }
  
⑧ consoleLogger.stop()
⑨ Constraint.enableAllSignals()

⓪ Creates an instance of MemoryLogger. This logger simply captures signals into an array, available via the recordings property. Notice that this logger isn't running yet.

① Creates and starts a ConsoleLogger. This logger simply prints a time-stamped debugDescription of the emitted value to the console, similar to how a conventional system logs messages. In contrast to the memory logger created in ⓪ - this logger is running and will process emitted signals. See Components for a list of available loggers.

② The console logger is set to filter out anything that is not a StringSignal - that's what actually gets logged when we call emit("with a string"). Different types of values are represented by different types of signal, falling back to the WrapperSignal for capturing arbitrary values. Signals can be easily extended to accomodate specific types of values and are portable. See Components for a list of available signals.

③ Control the flow of signals by defining constraints. The first constraint disables logging of all types of signals. The second constraint enables logging ErrorSignals, but only by the memoryLogger. The effect is that only ErrorSignals will be logged, and only by the memoryLogger. By default all types of signals are logger, which is the equivalent of +Signal.self as the sole constraint. See Filtering for more information on constraints.

④ Calling emit() without a value logs the execution context, capturing the stack trace leading to the call. This happens by creating and emitting a ContextSignal. Additionally, Beacon provides specialized signals for capturing information needed to symbolicate those stack traces.

⑤ It is possible to perform one-shot logging - that is, starting the logger only for the duration of a block closure.

⑥ Every form of emit() takes two additional and optional arguments: a list of Beacons on which to emit a signal (defaulting to Beacon.shared), and an arbitrary userInfo value.

⑦ Emits an ErrorSignal, wrapping the error. Notice that this variant of emit() has a named argument. There is a difference between emit(anError), which will emit a WrapperSignal, and emit(error: anError), which would emnit an ErrorSignal.

⑧ Since we started the console logger in ①, we should stop it whenever we're done with it.

⑨ Restore constraints back to their defaults.

Components

The framework provides the following building blocks:

Signal Description
ContextSignal Captures execution context at initialization site - e.g. name of containing method and a stack trace
ErrorSignal Captures an error and a stack trace that lead to it
StringSignal Captures a string - à la conventional logging systems
WrapperSignal Captures arbitrary value - be mindful of mutating values as some of the logging facilities may log at a later time
IdentitySignal Captures information about Beacon itself - such as version, current platform, architecture, etc
MachImageImportsSignal Captures addition and removal of MachO images - this is mainly to help with stack symbolication

Custom signals are typically implemented as subclasses of WrapperSignal as it provides some helpful machinery on top of the base Signal class.

Loggers Description
MemoryLogger Logs signals into an array of fixed size
ConsoleLogger Prints signals onto the console
JRPCLogger Sends signals to a JSON-RPC server (see Beacon-Server for a server implementation in Pharo)
FileLogger Logs signals to a file, with rotation support

Furthermore, there are a couple of abstract loggers:

  • StreamLogger writes out signals on an arbitrary OutputStream (e.g. FileLogger)
  • IntervalLogger provides buffered interface to writing out signals (e.g. JRPCLogger)

Stack Symbolication

A few signals, namely ErrorSignal and ContextSignal, capture stack traces that lead to the emission of the signal. In some cases, the binary may have its symbols stripped, and those stack traces will need to be symbolicated in order to make any sense of them. To help with that, Beacon uses two special signals: IdentitySignal and MachImageImportsSignal. The former captures the current running environment - operating system and processor architecture, while the latter captures insertion and removal of MachO images. Armed with both the architecture and load addresses of the binary and its dependencies it is possible to symbolicate those stack traces with a tool like atos.

Loggers can be configured to emit those signals:

let consoleLogger = ConsoleLogger(name: "Console")

// Will emit IdentitySignal when started
consoleLogger.identifiesOnStart = true

// Will emit MachImageImportsSignal for all loaded MachO images when started 
// and then track subsequently loaded and unloaded images 
consoleLogger.tracksMachImageImports = true

consoleLogger.start() 

Objective-C

The framework supports both Swift and Objective-C. There are, however, a few language-specific differences between the two. When it comes to emitting signals in Objective-C, the following macros are defined for convenience:

// emit context signal
BeaconEmit(beacons, userInfo);

// emit value
BeaconEmit(someObject, beacons, userInfo);

// emit error
BeaconEmitError(someError, beacons, userInfo);

The beacons argument expects either NSArray<Beacon*>* or nil, implying the shared beacon object.

When it comes to emitting custom signals, you'd have to either provide your own macros, or use this flow:

MySignal *signal = [MySignal new];
BeaconEmitSignal(signal, on: arrayOfBeacons, userInfo: aUserInfoDictionary)

Xcode Goodies

You can find a code snippet to simplify creation of custom signals here. Add it to your existing collection of Xcode snippets, if you're into that sort of thing...

GitHub

link
Stars: 2
Last commit: 10 weeks ago
Advertisement: IndiePitcher.com - Cold Email Software for Startups

Release Notes

3.0.1
1 year ago

Updated

  • Pulling out objc support into a separate framework
  • Enabling use in Swift Playgrounds
  • Synchronous logging to console

Full change log 3.0.0…3.0.1

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics