apple / swift-log Goto Github PK
View Code? Open in Web Editor NEWA Logging API for Swift
Home Page: https://apple.github.io/swift-log/
License: Apache License 2.0
A Logging API for Swift
Home Page: https://apple.github.io/swift-log/
License: Apache License 2.0
I've noticed that StreamLogHandler
prettify
s meta data as soon as it is set.
This seems like it is probably wasteful.
logLevel
] the prettified data will never be used.I propose that it would be better to prettify on first usage rather than when metadata is set.
The only downside I can think of is that this possibly shifts the prettification to a more critical moment in execution.
Example output:
2019-03-19 17:30:21.703 test-log[74560:524592] INF [com.apple.main-thread] [Tag1] > all done with result = true
2019-03-19 17:30:21.699 test-log[74560:524594] INF [com.apple.root.default-qos] [Tag2] > trace from DispatchQueue.global().async {}
This behaviour already implemented here: https://github.com/allright/ASLog/blob/master/ASLog.swift
When importing logging and using the a custom LogHandler, the app builds successfully
Logger
public protocol LogListener {
var isEnabled: Bool { get set }
var logLevel: Logger.Level { get set }
func logMessage(_ logDetails: ATHLogHandler.LogDetails)
}
Version: 1.2.0
swift --version && uname -a
)Swift 5.3
macOS 11
I know this is on beta software and you're not required to support that anytime soon, but just wanted to put it out there.
I'd like to use Lock
in a custom LogHandler implementation but am unable to because Lock
is declared as internal
in Locks.swift
. Was Lock
intended to be used like this?
Under "Special cases" in the API docs here, the example implementation LogHandlerWithGlobalLogLevelOverride
has a property overrideLock = Lock()
.
This is not possible if the custom LogHandler implementation (e.g. LogHandlerWithGlobalLogLevelOverride
) is defined in another module as Lock
is internal and not visible there.
version: 0.0.0
swift --version && uname -a
)Apple Swift version 4.2.1 (swiftlang-1000.11.42 clang-1000.11.45.1) Target: x86_64-apple-darwin18.6.0
This is the "later" follow up ticket to the "take out metadata
to start with and make opaque later #31".
One use case of being able to "take the entire metadata" is keeping around data that is set for the lifetime of a resource, so a library may want to "1) store it 2) run user code, allowing it to shadow or remove metadata etc 3) restore the metadata for the next invocation"
This pattern is sometimes useful, and would be nice if we could support it, esp since without a "give me the metadata" a framework can not implement this without knowing all the keys that it should keep+restore.
Example test:
func testMDCStyleMetadata() {
let testLogging = TestLogging()
LoggingSystem.bootstrapInternal(testLogging.make)
var logger = Logger(label: "\(#function)")
logger[metadataKey: "always"] = "there"
func userCode() {
logger[metadataKey: "always"] = "other"
logger.info("hello world!")
testLogging.history.assertExist(level: .info, message: "hello world!", metadata: ["always": "other"])
}
let old = logger.metadata
userCode()
logger.metadata = old
func userCode2() {
logger.info("hello world!")
testLogging.history.assertExist(level: .info, message: "hello world!", metadata: ["always": "there"])
}
userCode2()
}
I use Log
and Logger
API for general logging needs in my custom packages on Linux and iOS. Everything works great.
On iOS I initialize the Log.logger
(for historical reasons and cooperation with legacy code) with my custom implementation of https://github.com/CocoaLumberjack/CocoaLumberjack based Logger
implementation.
Also works great. Except for one thing. CocoaLumberjack is high perf and highly async, and when my app crashes controllably, the last few most important log statements are lost.
For that CocoaLumberjack provides a method DDLog.flushLog
https://github.com/CocoaLumberjack/CocoaLumberjack/blob/832adf8139ee8b9810683a110a2318a9ea225a5a/Sources/CocoaLumberjack/include/CocoaLumberjack/DDLog.h#L367.
I would like to open a PR to propose addition of Logger.flush()
and Log.flush()
methods that would allow my app to invoke these methods and propagate them down in order to make sure logging is flushed in cases where app is terminated in a controlled manner.
What do you think? How you achieve the same?
log statements are immediately written to stream, so if a process crashes we dont lose logs
default stream based logger has default flush behavior which is not immediate
somewhat related: https://bugs.swift.org/browse/SR-1127
I expected that by setting different levels on the different handlers each one would be respected.
Looks like logs are being handled by both handlers only when the log function used is above the levels of both handlers.
In the following code snippet, MyLogger
sets its own log level to .info
in its init
call.
let stdoutLogger: (String) -> StreamLogHandler = { label in
var logger = StreamLogHandler.standardOutput(label: label)
logger.logLevel = .trace
return logger
}
LoggingSystem.bootstrap { label in
return MultiplexLogHandler([MyLogger(), stdoutLogger(label)])
}
log.trace("Test Trace")
log.info("Test Info")
The output in the debugger is only:
2020-06-25T13:48:21-0400 info: Test Info
1.2.0
swift --version && uname -a
)Swift 5.2 on iOS 13.5.1
done on the forums thread regarding log levels.
Currently MultiplexLogHandler mutates all registered handlers to the same log level; permitting fallthrough granularity would be useful for distributed systems.
Example:
1st Handler: logLevel: .trace ... // -> stdout pipe, local storage
2nd Handler: logLevel: .info ... // -> external endpoint, analytic processing backend
3rd Handler: logLevel: .critical .. // -> external endpoint, urgent sysadmin alert gateway
swift-log 1.1.1
add link to cocoapod to readme (see #74 )
I know that this opens the floodgates of expanding the formatting capabilities of the basic dumb "out of the box" log handler this package ships with... but how do we feel providing a little better experience?
This would make debugging using SwiftLog in packages such as RediStack much easier as it wouldn't necessitate creating a custom formatter or pulling in a dependency just for development
2020-09-18T22:51:14-0700 trace RediStack.RedisConnection : connection created
rdstk_conn_id=AF0DFDB1-1C6F-43A4-B8C1-6AA09E84C363
2020-09-18T22:51:14-0700 trace RediStack.RedisConnection : received subscribe request
rdstk_conn_id=AF0DFDB1-1C6F-43A4-B8C1-6AA09E84C363
2020-09-18T22:51:14-0700 trace RediStack.RedisConnection : adding subscription
rdstk_ps_target=Channels 'multi_channel_test'
rdstk_conn_id=AF0DFDB1-1C6F-43A4-B8C1-6AA09E84C363
2020-09-18T22:51:14-0700 debug RediStack.RedisConnection : not in pubsub mode, moving to pubsub mode
rdstk_conn_id=AF0DFDB1-1C6F-43A4-B8C1-6AA09E84C363
2020-09-18T22:51:14-0700 trace RediStack.RedisConnection : handler added, adding subscription
rdstk_conn_id=AF0DFDB1-1C6F-43A4-B8C1-6AA09E84C363
2020-09-18T22:51:14-0700 trace RediStack.RedisConnection : successfully entered pubsub mode
rdstk_conn_id=AF0DFDB1-1C6F-43A4-B8C1-6AA09E84C363
2020-09-18T22:51:14-0700 trace RediStack.RedisConnection : rdstk_conn_id=AF0DFDB1-1C6F-43A4-B8C1-6AA09E84C363 connection created
2020-09-18T22:51:14-0700 trace RediStack.RedisConnection : rdstk_conn_id=AF0DFDB1-1C6F-43A4-B8C1-6AA09E84C363 received subscribe request
2020-09-18T22:51:14-0700 trace RediStack.RedisConnection : rdstk_ps_target=Channels 'multi_channel_test' rdstk_conn_id=AF0DFDB1-1C6F-43A4-B8C1-6AA09E84C363 adding subscription
2020-09-18T22:51:14-0700 debug RediStack.RedisConnection : rdstk_conn_id=AF0DFDB1-1C6F-43A4-B8C1-6AA09E84C363 not in pubsub mode, moving to pubsub mode
2020-09-18T22:51:14-0700 trace RediStack.RedisConnection : rdstk_conn_id=AF0DFDB1-1C6F-43A4-B8C1-6AA09E84C363 handler added, adding subscription
2020-09-18T22:51:14-0700 trace RediStack.RedisConnection : rdstk_conn_id=AF0DFDB1-1C6F-43A4-B8C1-6AA09E84C363 successfully entered pubsub mode
package working as a dependency in swift 5.2
You get this error:
dependency 'Logging' in target 'BestExampleApp' requires explicit declaration; reference the package in the target dependency with '.product(name: "Logging", package: "swift-log")'
Following the README doesn't quite work. as the error says, you have to put the package name in with swift 5.2 so from the readme this section would change:
.target(name: "BestExampleApp", dependencies: ["Logging"]),
^^^ fails, so should become vvv
.target(name: "BestExampleApp", dependencies: [.product(name: "Logging", package: "swift-log")]),
In spite of #135 (comment), I think the was API actually broken in #135:
This:
@inlinable
public func debug(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
...
}
...is not the same as:
@inlinable
public func debug(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
source: @autoclosure () -> String? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
...
}
We need to retain the old function signatures in addition to the new ones and just forward the implementation from the old to the new.
Title and end goal of ticket changed to add some examples, see discussion below for detail (edit: ktoso)
I was looking into creating a FileLogHandler
but I am unsure of a safe and convenient way to go about doing that due to issues inherent with logging to files vs something like stdout with the current limitations of this framework.
For example, there is no available throwable factory overload and nowhere is it documented that a logger requires an initializer that takes a single String argument. Looking through the code it becomes much easier to see that you need some kind of an initializer that takes a single string argument. Why is there no official protocol requirement for the initializer if this is clearly a requirement?
I want to make a FileLogHandler, but with the current standard functionality, everyone would just be accustomed to passing in a label. I could just log in a common location like /var/log on linux, but people should be able to specify a custom file location if they so choose.
The Logger initializer that takes a LogHandler argument is internal so I cannot use that and the restrictions on the factory signature are too restrictive to allow this. I could make the String parameter the path to the log file, but since it is not throwable or nullable I cannot do any sort of validation that the path exists or is writable by the current process. Do I just silently fail? Should I just print
a message?
Why are there no throwable/nullable overloads for the factory? or why is there no ability to use a custom factory signature? The fileprivate restrictions make it more difficult to provide my own handy extensions to achieve my desired functionality.
Is there something I'm missing here or was there just massive oversight when designing this? From what I can tell it would be extremely difficult to set up a logger that sends logs to an external service requiring connection parameters and there's no way you could validate those parameters during initialization of the handler.
On the forums , a few have rightly pointed out that currently, there's two places to get/set the metadata:
logger[metdataKey: "foo"] = "bar"
and
logger.metadata["foo"] = "bar"
The intention is to always use the former (logger[metdataKey: "foo"] = "bar"
) unless you want to store/retrieve the whole metadata storage, for example for MDC.
But as it was correctly pointed out, if that's the case we could/should make it opaque.
My proposal for right now is that the LogHandler
s need to support both but the Logger
should only expose the subscript. In a new minor version that we can release anytime, we could then add a back a well-designed story for MDC and friends.
When I perform someLogger.trace("hello", function: "someFunction()")
I expect that the function parameter that reaches the underlying LogHandler will be, someFunction()
.
In practice, the function parameter will be trace(_:metadata:file:function:line:)
.
SwiftLog 1.4.1
swift --version && uname -a
)Apple Swift version 5.3.2 (swiftlang-1200.0.45 clang-1200.0.32.28)
Target: x86_64-apple-darwin19.6.0
Darwin Sauls-MacBook-Pro-16-inch-2019 19.6.0 Darwin Kernel Version 19.6.0: Tue Jan 12 22:13:05 PST 2021; root:xnu-6153.141.16~1/RELEASE_X86_64 x86_64
When adding back the accidentally removed functions on #152, the source-less trace
call doesn't pass the value of function
when calling trace
with the source
parameter. As a result, the value of function
is always trace(_:metadata:file:function:line:)
for all calls to trace
where a source is not set.
I recently adopted swift-log
. Now pod lib lint
is failing with the following error.
import Logging
^
** BUILD FAILED **
The following build commands failed:
CompileSwift normal i386
CompileSwiftSources normal i386 com.apple.xcode.tools.swift.compiler
(2 failures)
Is swift-log
only for server-side use? I believe this should be available on all platforms where swift is available may be as part of core-libs
.
1.1.0
swift --version && uname -a
)Apple Swift version 5.0.1 (swiftlang-1001.0.82.4 clang-1001.0.46.5)
Target: x86_64-apple-darwin18.6.0
Darwin Prafulls-MacBook-Pro.local 18.6.0 Darwin Kernel Version 18.6.0: Thu Apr 25 23:16:27 PDT 2019; root:xnu-4903.261.4~2/RELEASE_X86_64 x86_64
When adding metadata to loggers and using StreamLogHandler
, the order of key/value pairs are not maintained. Should this be the case or is it intentional?
swift-log/Sources/Logging/Logging.swift
Lines 291 to 303 in b33865f
Notably, GCP Stackdriver logging with google-fluentd allows you to record your log entries as JSON, see:
https://cloud.google.com/logging/docs/agent/configuration#streaming_logs_from_additional_inputs
A log entry appended to a log file using this type of logging looks like '{"code": 1001, "message": "This is a log from the log file at test-structured-log.log", "isBool": true }'
For this to work as intended for this particular cases, the logger metadata values would need to properly translate to all json raw values, i.e null, bool, number and string.
As of right now, a LogHandler
could certainly be created for GCP stackdriver JSON format logging with the raw values being restricted to String only, but that would be a subpar solution that stops the Stackdriver platform from reaching its full potential.
It seems like addressing this, if desired, would incur significant breaking changes to swift-log, but given that some server side framework like Vapor 4 are about to make swift-log defacto, I think it's worth giving a thought :)
Hello,
This is more like a pitch, not an issue at all, and it’s probably too late. However, I’d like to have a custom handler for os_log
, something like this works fine for debugging purposes, even though it’s using private API:
import os
import _SwiftOSOverlayShims
...
func log(
level: Logger.Level, message: Logger.Message, metadata: Logger.Metadata?,
file: String, function: String, line: UInt, dso: UnsafeRawPointer, args: CVarArg...)
{
let ra = _swift_os_log_return_address()
message.withUTF8Buffer { (buf: UnsafeBufferPointer<UInt8>) in
buf.baseAddress?.withMemoryRebound(to: CChar.self, capacity: buf.count) { str in
withVaList(args) { valist in
_swift_os_log(dso, ra, self.log_from_label(), self.type_from_level(level), message, valist)
}
}
}
}
Unfortunately, the current log
signature misses #dsoHandle
and args
, using #file
, #function
and #line
instead. What do you think about adding two more arguments to the log
function? Or maybe you have some alternative suggestion?
Thanks in advance!
I don't see Logging 1.1.0 on CocoaPods.org: https://cocoapods.org/pods/Logging
Is this the right channel to remind about publishing the 1.1.0 podspec in https://github.com/CocoaPods/Specs?
Besides, it might be a good idea to add a link to https://cocoapods.org/pods/Logging to the README, especially in the time being their search apparently fails to index newly added pods. 😕
Given this package layout:
.
|- Package.swift
|- Sources
|- ModuleA
|- Subdir1
|- Impl.swift
|- ModuleB
|- Subdir1
|- Impl.swift
If I perform someLogger.info("hello")
in ModuleA/Subdir1/Impl.swift
and someLogger.info("world")
in ModuleB/Subdir1/Impl.swift
, I expect that the source
parameters which reach the underlying LogHandler
will be, respectively, ModuleA
and ModuleB
.
In practice, the source
parameter will be Subdir1
in both calls.
SwiftLog 1.3.0
swift --version && uname -a
)Apple Swift version 5.3 (swiftlang-1200.0.16.9 clang-1200.0.22.5)
Target: x86_64-apple-darwin19.5.0
Darwin macbookpro.local 19.5.0 Darwin Kernel Version 19.5.0: Tue May 26 20:41:44 PDT 2020; root:xnu-6153.121.2~2/RELEASE_X86_64 x86_64
This is the natural result of using what is essentially basename(dirname(#file))
as the default for source
in Logger.currentModule(filePath:)
(
swift-log/Sources/Logging/Logging.swift
Line 762 in 57c6bd0
Logger.log(level:_:metadata:source:file:function:line:)
(swift-log/Sources/Logging/Logging.swift
Line 75 in 57c6bd0
Logger
's log()
method, it is difficult to override it via, for example, a project's choice of LogHandler
, with the result that this new source
parameter (otherwise a very useful input) must be ignored in favor of continuing to use such problematic solutions as parsing #file
or #filePath
(the irony that this behavior is also the cause of this very issue is not lost on me!).It would be great if the default StreamLogHandler
would honour LOGLEVEL environment variables.
To change the loglevel I currently have to do this dance, right?:
LoggingSystem.bootstrap { label in
var handler = StreamLogHandler.standardOutput(label: label)
handler.logLevel = .trace
return handler
}
Because StreamLogHandler
hardcodes its level to .info
:
public var logLevel: Logger.Level = .info
It would be nice if that would at least default to the setting of the LOGLEVEL
environment variable, ideally w/ supporting LOGLEVEL_$label
as well.
Essentially:
public var logLevel: Logger.Level = {
let env = ProcessInfo.processInfo.environment
return Logger.Level(rawValue: "LOGLEVEL_" + label)
?? Logger.Level(rawValue: "LOGLEVEL")
?? .info
}()
Maybe w/ a little more caching.
Just a question, can I get the logs written to a log file locally with in the App directory?
swift-log/Sources/Logging/Logging.swift
Line 828 in 12d3a86
We should use fwrite
and not fputs
here... Usually doesn't matter unless we print a 0 byte which could be a hard to debug thing if we lose everything after the 0 byte with fputs
.
The two recently accepted by the SSWG API packages both use the "bootstrap at system start style" to select a (logger | metrics) backend.
The Logging API requires:
LoggingSystem.bootstrap(MyLogHandler.init)
so exposing the LogHandler directly.
At the same time we say that "log handler is internal detail, don't worry about it."
The style is similar yet notably different in the metrics API:
MetricsSystem.bootstrap(SelectedMetricsImplementation())
as the SelectedMetricsImplementation
often may / will contain more state than only being a function that creates a stateless metric.
Note as well that one can obtain the factory like MetricsSystem.factory
which is important in order to implement more advanced features e.g. removing metrics.
I would like to propose re-aligning the style of how one performs bootstrap in both those APIs, so the SSWG APIs have a shared "feel" and meaning to words used -- e.g. that *Handler
should never really appear in "user land" unless there is good reasons to.
Proposal:
bootstrap(SimpleLoggingFactory())
etcAnother point for this would be that I can definitely imagine using such factory to keep state around such as connections etc, if the logger wanted to send metrics to an external service for gathering etc.
WDYT?
When handling errors, I would like to be able to log error.localizedDescription
.
logger.error(_:)
takes in the string and logs it
Error:
Cannot convert value of type 'String' to expected argument type 'Logger.Message'
Logger.error(_:)
with error.localizedDescription
GH Link to line/project I was working on when I found out
From Package.resolved
:
"revision": "a64dd7dafc06bcecc43ec4dff0d71ef797247456"
"version": "1.0.0"
swift --version && uname -a
)Apple Swift version 5.0.1 (swiftlang-1001.0.82.4 clang-1001.0.46.5)
Target: x86_64-apple-darwin18.5.0
Darwin LotU-MacBook-Pro.local 18.5.0 Darwin Kernel Version 18.5.0: Mon Mar 11 20:40:32 PDT 2019; root:xnu-4903.251.3~3/RELEASE_X86_64 x86_64
A new logger backend to https://firebase.google.com/ ( see https://firebase.google.com/docs/ios/setup )
Thanks a lot @leisurehound for sharing this 🎉
Would you be able to add it to our list of conforming implementations https://github.com/apple/swift-log#selecting-a-logging-backend-implementation-applications-only ?
If you prefer I add the link let me know, usually we leave it up to the backend authors :)
not sure what is the best practice here. Should I be using os.log or should I be using swift-log?
Does team consider CocoaPods / Carthage support? I can make PR
A simple question.
Is it able to write to file?
I experience a Catalyst problem - is there a solution or workaround?
I expected the library to work also in a combined iOS/macOS project (Catalyst).
I created a lightweight wrapper around the lib and put it into a "Log" Swift Package.
For "import Logging", the compiler tells:
"Logging is not available when building for Mac Catalyst. Consider using
#if !targetEnvironment(macCatalyst) to conditionally import this framework."
The Package.swift
content for the Log
wrapper:
import PackageDescription
let package = Package(
name: "Log",
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "Log",
targets: ["Log"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "Log",
dependencies: []),
.testTarget(
name: "LogTests",
dependencies: ["Log"]),
]
)
swift --version && uname -a
)Xcode 11.0 (11A419c) / Swift 5 / macOS 10.15 / iOS 13.0
Let's link to https://github.com/Brainfinance/StackdriverLogging by @jordanebelanger
Would you want to submit a PR adding a link to it to the https://github.com/apple/swift-log#selecting-a-logging-backend-implementation-applications-only section @jordanebelanger?
Hello!
I noticed that the framework can be used in iOS, from closed issues and from the fact that I was able to add the package in Xcode. Should the README be updated to reflect this functionality? Do other features need to be implemented before updating it?
Milestones should be in sync with released versions.
We released https://github.com/apple/swift-log/releases 1.1.1 but no such milestone exists.
1.1.0 exist and is not closed. etc.
When closing tickets, always assign a milestone;
When releasing a version, ensure tickets or PRs if no ticket present for PR are assigned to milestone; Close milestone, link to it from release notes.
I think it matters a lot to be clean and consistent here.
We should start with cleaning up the existing issues / releases.
I can take this on.
Neal did a nice library that allows capturing and asserting on the logs https://github.com/neallester/swift-log-testing
Discussion thread: https://forums.swift.org/t/new-swift-log-testing-loghandler/30980
It's quite minimal but the approach is quite nice -- I'm occasionally a fan of such tests as well, when we want to ensure that some library logs useful good information in certain conditions etc.
swift-log/Sources/Logging/Logging.swift
Lines 216 to 230 in 306f432
@weissi Should Logger.Message conform to ExpressibleByStringInterpolation
? I wonder if you can pass a string interpolation for logging with this definition?
Long long ago, Xcode console was colorful by plug-ins.
Now Xcode not support it, we use ANSI format and other software to look up the log like terminal. detail
it's time to change it
best wishes
I'm looking at being an early adopter of SwiftLog because it looks good and I'm tired of solving the logging issue every time I start a new project. It occurs to me though, that StdoutLogHandler
should be public
but it is marked as internal
.
The StdoutLogHandler
is great because it provides out-of-the-box console logging to get up and moving fast and prevents the need to re-implement that, but being marked internal
means that as soon as the use-case gets a little more interesting, I'd have to write my own console log handlers. The use case I'm referring to really just looks like this:
LoggingSystem.bootstrap {
MultiplexLogHandler([StdoutLogHandler(label: $0), RemoteLogHandler(label: $0)])
}
I suppose MultiplexLogHandler
could have some kind of configurability to implicitly create a StdoutLogHandler
in addition to whichever log handlers are passed in. This could be an alternative to exposing StdoutLogHandler
if that is truly the desire.
It's also worth pointing out that StdoutLogHandler
's initializer is explicitly marked public. So I'm wondering if this has been an oversight.
I can open a PR to make a change if needed.
Would you want to send in a PR to add https://github.com/DnV1eX/GoogleCloudLogging to the list @DnV1eX? Thanks in advance!
LoggingSystem.bootstrap
must only be called once per process. One might assume to bootstrap the logger in XCTestCase.setUp
, but this can be called multiple times and will result in an assertion.
To get around this, a global lazy variable can be used that is asserted in each setUp
method. It will only be run once, the first time it is accessed, allowing for logging to be configured in XCTests:
Declare the global variable like so:
let isLoggingConfigured: Bool = {
LoggingSystem.bootstrap { label in
var handler = StreamLogHandler.standardOutput(label: label)
handler.logLevel = .debug
return handler
}
return true
}()
Then, in your XCTestCase
s, use like so:
import XCTest
final class FooTests: XCTestCase {
override func setUp() {
XCTAssert(isLoggingConfigured)
...
}
}
We should consider documenting this or providing some sort of helper.
@weissi One somewhat subtle aspect of using custom string interpolation is that we cannot directly log a dynamic string right? E.g. if msg
is of type String
, we have to use logger.debug("\(msg)")
to log it. Just wondering if adding a test case for this will help document this behavior.
I am using Swift-Log initialized by LoggingSystem.bootstrap(StreamLogHandler.standardError)
, but on log messages with log level >= info
are shown on the console window of XCode 12.0.1.
How can I configure the log level of the log messages shown on the console?
Thanks.
Please update podspec for SwiftLog 1.2.0 (related to #74)
The proposal from SSWG: Server Logging API suggest using String
for parameters like #file
and #function
.
func log(level: Logger.Level,
message: Logger.Message,
metadata: Logger.Metadata?,
file: String, function: String, line: UInt)
But is using StaticString
a better design in practice?
Log functions take String
for #file
and #function
. But some logging frameworks (backends) take StaticString
for these parameters.
The main problem is String
can not convert to StaticString
, so for those frameworks take StaticString
as parameters, there is no way to build a LogHandler
for them.
The only workaround I found is discard these information. For those backends don't care about the #file
or #function
this will be fine, like swift-log-oslog.
However, the others will always get the #file
and #function
in LogHandler
, not the actual place in the program. This will be a big problem.
If we changed the parameter type from String
to StaticString
, those backends require StaticString
can take it without problems. And those require String
can easily convert it to what they want. A simple extension can do the trick.
extension StaticString {
@inlinable
var stringValue: String {
self.withUTF8Buffer { String(decoding: $0, as: UTF8.self) }
}
}
The cons of this change is, it breaks the API compatibility, log handler providers need to update their code for this chage.
Any suggestions about this idea?
PR #135 introduces a source
parameter that defaults to the module in which a log statement was made.
See there for a long discussion why that matters -- long story short: it enables us to share a logger instance with an un-changing label, yet still keep the "this was logged from sub-component X (the module)".
We also considered adding a LoggerWithSource
back then, however we decided that there are few use-cases about it today and we want to take it slow adding API. This ticket is to collect interest if this type should also ship with the swift-log library or not necessarily, as we learn about usage patterns.
The LoggerWithSource
allows for overriding with a hardcoded source e.g. "thread-pool-x" or something the source of the log message. We concluded however that in most situations such things can be handled with metadata. If we see that overriding a source becomes
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Logging API open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift Logging API project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of Swift Logging API project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
/// `LoggerWithSource` shares the same API as `Logger`, except that it automatically parses on the supplies `source`
/// instead of requiring the user to supply source when logging a message.
///
/// - info: Do not accept or pass `LoggerWithSource` to/from other modules. The type you use publicly should always be
/// `Logger`.
public struct LoggerWithSource {
/// The `Logger` we are logging with.
public var logger: Logger
/// The source information we are supplying to `Logger`.
public var source: String
/// Construct a `LoggerWithSource` logging with `logger` and `source`.
@inlinable
public init(_ logger: Logger, source: String) {
self.logger = logger
self.source = source
}
}
extension LoggerWithSource {
/// Log a message passing the log level as a parameter.
///
/// If the `logLevel` passed to this method is more severe than the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen. The `source` is the one supplied to the initializer of `LoggerWithSource`.
///
/// - parameters:
/// - level: The log level to log `message` at. For the available log levels, see `Logger.Level`.
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func log(level: Logger.Level,
_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.logger.log(level: level,
message(),
metadata: metadata(),
source: self.source,
file: file, function: function, line: line)
}
/// Add, change, or remove a logging metadata item.
///
/// The `source` is the one supplied to the initializer of `LoggerWithSource`.
///
/// - note: Logging metadata behaves as a value that means a change to the logging metadata will only affect the
/// very `Logger` it was changed on.
@inlinable
public subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
get {
return self.logger[metadataKey: metadataKey]
}
set {
self.logger[metadataKey: metadataKey] = newValue
}
}
/// Get or set the log level configured for this `Logger`.
///
/// The `source` is the one supplied to the initializer of `LoggerWithSource`.
///
/// - note: `Logger`s treat `logLevel` as a value. This means that a change in `logLevel` will only affect this
/// very `Logger`. It it acceptable for logging backends to have some form of global log level override
/// that affects multiple or even all loggers. This means a change in `logLevel` to one `Logger` might in
/// certain cases have no effect.
@inlinable
public var logLevel: Logger.Level {
get {
return self.logger.logLevel
}
set {
self.logger.logLevel = newValue
}
}
}
extension LoggerWithSource {
/// Log a message passing with the `Logger.Level.trace` log level.
///
/// The `source` is the one supplied to the initializer of `LoggerWithSource`.
///
/// If `.trace` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func trace(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.logger.trace(message(),
metadata: metadata(),
source: self.source,
file: file,
function: function,
line: line)
}
/// Log a message passing with the `Logger.Level.debug` log level.
///
/// The `source` is the one supplied to the initializer of `LoggerWithSource`.
///
/// If `.debug` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func debug(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.logger.debug(message(),
metadata: metadata(),
source: self.source,
file: file,
function: function,
line: line)
}
/// Log a message passing with the `Logger.Level.info` log level.
///
/// The `source` is the one supplied to the initializer of `LoggerWithSource`.
///
/// If `.info` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func info(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.logger.info(message(),
metadata: metadata(),
source: self.source,
file: file,
function: function,
line: line)
}
/// Log a message passing with the `Logger.Level.notice` log level.
///
/// The `source` is the one supplied to the initializer of `LoggerWithSource`.
///
/// If `.notice` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func notice(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.logger.notice(message(),
metadata: metadata(),
source: self.source,
file: file,
function: function,
line: line)
}
/// Log a message passing with the `Logger.Level.warning` log level.
///
/// The `source` is the one supplied to the initializer of `LoggerWithSource`.
///
/// If `.warning` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func warning(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.logger.warning(message(),
metadata: metadata(),
source: self.source,
file: file,
function: function,
line: line)
}
/// Log a message passing with the `Logger.Level.error` log level.
///
/// The `source` is the one supplied to the initializer of `LoggerWithSource`.
///
/// If `.error` is at least as severe as the `Logger`'s `logLevel`, it will be logged,
/// otherwise nothing will happen.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func error(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.logger.error(message(),
metadata: metadata(),
source: self.source,
file: file,
function: function,
line: line)
}
/// Log a message passing with the `Logger.Level.critical` log level.
///
/// The `source` is the one supplied to the initializer of `LoggerWithSource`.
///
/// `.critical` messages will always be logged.
///
/// - parameters:
/// - message: The message to be logged. `message` can be used with any string interpolation literal.
/// - metadata: One-off metadata to attach to this log message
/// - file: The file this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#file`).
/// - function: The function this log message originates from (there's usually no need to pass it explicitly as
/// it defaults to `#function`).
/// - line: The line this log message originates from (there's usually no need to pass it explicitly as it
/// defaults to `#line`).
@inlinable
public func critical(_ message: @autoclosure () -> Logger.Message,
metadata: @autoclosure () -> Logger.Metadata? = nil,
file: String = #file, function: String = #function, line: UInt = #line) {
self.logger.critical(message(),
metadata: metadata(),
source: self.source,
file: file,
function: function,
line: line)
}
}
func testAllLogLevelsWorkWithOldSchoolLogHandlerButSourceIsNotPropagated() {
let testLogging = OldSchoolTestLogging()
var logger = LoggerWithSource(Logger(label: "\(#function)",
factory: testLogging.make),
source: "my-fancy-source")
logger.logLevel = .trace
logger.trace("yes: trace")
logger.debug("yes: debug")
logger.info("yes: info")
logger.notice("yes: notice")
logger.warning("yes: warning")
logger.error("yes: error")
logger.critical("yes: critical")
// Please note that the source is _not_ propagated (because the backend doesn't support it).
testLogging.history.assertExist(level: .trace, message: "yes: trace", source: "no source")
testLogging.history.assertExist(level: .debug, message: "yes: debug", source: "no source")
testLogging.history.assertExist(level: .info, message: "yes: info", source: "no source")
testLogging.history.assertExist(level: .notice, message: "yes: notice", source: "no source")
testLogging.history.assertExist(level: .warning, message: "yes: warning", source: "no source")
testLogging.history.assertExist(level: .error, message: "yes: error", source: "no source")
testLogging.history.assertExist(level: .critical, message: "yes: critical", source: "no source")
}
func testAllLogLevelsWorkOnLoggerWithSource() {
let testLogging = TestLogging()
LoggingSystem.bootstrapInternal(testLogging.make)
var logger = LoggerWithSource(Logger(label: "\(#function)"), source: "my-fancy-source")
logger.logLevel = .trace
logger.trace("yes: trace")
logger.debug("yes: debug")
logger.info("yes: info")
logger.notice("yes: notice")
logger.warning("yes: warning")
logger.error("yes: error")
logger.critical("yes: critical")
testLogging.history.assertExist(level: .trace, message: "yes: trace", source: "my-fancy-source")
testLogging.history.assertExist(level: .debug, message: "yes: debug", source: "my-fancy-source")
testLogging.history.assertExist(level: .info, message: "yes: info", source: "my-fancy-source")
testLogging.history.assertExist(level: .notice, message: "yes: notice", source: "my-fancy-source")
testLogging.history.assertExist(level: .warning, message: "yes: warning", source: "my-fancy-source")
testLogging.history.assertExist(level: .error, message: "yes: error", source: "my-fancy-source")
testLogging.history.assertExist(level: .critical, message: "yes: critical", source: "my-fancy-source")
}
func testLoggerWithSource() {
let testLogging = TestLogging()
LoggingSystem.bootstrapInternal(testLogging.make)
var logger = Logger(label: "\(#function)").withSource("source")
logger.logLevel = .trace
logger.critical("yes: critical")
testLogging.history.assertExist(level: .critical, message: "yes: critical", source: "source")
}
snippets above are from the impl by @weissi.
When logging using different loggers, e.g.:
let one = Logger(label: "one")
let two = Logger(label: "two")
I would expect the labels one
and two
to appear in log output.
This is not the case with the default StdoutLogHandler
:
internal struct StdoutLogHandler: LogHandler {
private let lock = Lock()
public init(label: String) {} // does not store label
I know the StdOutLogHandler is meant to be super minimal but this seems more like an omission?
Label is not included in log statements logged through to default std out handler.
I have a question on standards of usage. When one is using swift-log across multiple different packages, should one use the same Logger instance across these packages or is it acceptable to use different instances? If using the same instance, is there a standard means to propagate that instance across packages? (e.g., some kind of dependency injection?).
Thanks,
Chris.
I'm intending to cut 1.0.0
(and 0.1.0
for Swift 4.2 support) tonight 2019-04-09 (GMT).
The intended meaning of 1.0.0
is:
and nothing else :)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.