I would be happy with something like the API you proposed here. I think it would get the job done effectively.
But there is one thing I miss from this implementation:
If we were to create a generic Context
or Activity
type whose sole purpose was to capture and relate context, we could re-use such a type for many applications going forward. In other words, logging isn't the only thing that benefits from explicit contexts. The solution above allows for explicit contexts with logs by passing around an initialized logger. This is great, but if we also want explicit metrics, then we now need to pass around two things.
If I were to propose an API, I would do something very similar to yours, but with one small change regarding context:
public protocol Logger {
public func log(_ level: LogLevel, _ message: String, _ ctx: Context?)
}
public enum LogLevel {
case info
case warn
case error
}
public enum Log {
private static var logger: Logger?
public static func info(_ message: String, _ ctx: Context? = nil) {
logger?.log(.info, message, ctx)
}
public static func warn(_ message: String, _ ctx: Context? = nil) {
logger?.log(.warn, message, ctx)
}
public static func error(_ message: String, _ ctx: Context? = nil) {
logger?.log(.error, message, ctx)
}
}
public struct StdoutLogger: Logger {
...
}
// pseudo framework code
final class VaporResponder: HTTPResponder {
let app: UserApplication
func respond(to req: HTTPRequest) -> Future<HTTPResponse> {
req.ctx["unique"] = UUID().description
req.ctx["start_time"] = Date().description
return app.respond(to: req)
}
}
// user example
Log.logger = StdoutLogger()
router.get("hello") { req in
Log.info("Hello called!", req.ctx)
return "world"
}
// log would look something like
[ INFO ] Hello called! (unique: ..., start_time: ...)
That said, I'm definitely not against @johannesweiss's example. I think that would work quite well. Just wanted to share some ideas about the utility of a generic Context
type.