Hi,
Over the past month, I have been playing with BodyMacro
, which we gained access to with the release of Swift 6. I really enjoyed it, so as a side project, I created an MVP that I would like to share with you now.
First thing that came to my mind when I read the proposal and played with it was a logging library. However, I didn’t want to limit anyone to my logging of choice, so I wanted the design to allow anyone to incorporate logic of their choice, be it Sentry, swift-log or any other library. By default it uses os_log
.
swift-loggable comes with three macros (technically), but the third one is just meant to ignore logging for a function :)
@Logged
is a MemberAttributeMacro
. It can be attached, for example, to a class, and it will provide every function with a @Log
annotation - which I will cover briefly in a second. If we don’t want a function logged, the third macro comes into play - @Omit
, which prevents the marked function from being annotated with @Log
. Both @Logged
and @Log
can take an argument of type Loggable
. Loggable
is a class that we can inherit from and override its functions to implement our own logic. I’m not yet sure if this design choice was the best, but it allowed for a nicer syntax; I covered a little bit more in the README. When a custom Loggable
is passed to @Logged
e.g., @Logged(using: .custom)
, it will propagate our custom logic implementation to the functions within its scope.
@Log
, the heart of swift-loggable, is our BodyMacro
. It was a bit tricky to come up with a way to log a function because there are many possible combinations of function implementations one can come up with. Under the hood, a copy of the original function declaration is made, which allowed me to capture thrown errors or values returned by the function. As of now, it supports static, throwing, async, and generic functions with inout parameters, parameters preceded by the @autoclosure attribute and, of course, regular ones.
Usage examples
Logging with swift-loggable is a simple as annoting type with @Logged
, it will automatically add @Log
annotation to every function inside.
@Logged
struct Foo {
// ...
}
If you don’t want to log a function that is located in a scope annotated with @Logged
, mark it with @Omit
, it will be ignored when the macro is expanded.
@Logged
struct Foo {
func bar() {
// ...
}
@Omit
func baz() {
// ...
}
}
When you have to log only specific functions, use the @Log
macro, e.g.:
struct Foo {
@Log
func bar() {
// ...
}
}
As mentioned earlier, you can pass custom logging logic to both the @Logged
and @Log
macros. While the README covers this topic in detail, it essentially works as follows:
@Logged(using: .custom)
struct Foo {
func bar() {
// ...
}
}
Thank you for taking the time to read this. I’m looking forward to hearing your thoughts on this MVP.