Pitch: Introduce #module
to get the current module name
Implementation: pending (will do if pitch is successful enough)
Motivation
Currently Swift has a number "magic identifiers": #fileID
, #file
, #filePath
, #file
, #function
, #line
, #column
, and #dsohandle
which are extremely useful, for example for debugging and logging.
In log output, it's a common practise to output the filename (and sometimes the line number) where the log message was emitted from. In most cases however, only the basename of a file is logged, ie. if a log messages originates from /Users/me/MyProject/Sources/MyModule/MySubfolder/BestFile.swift
, then commonly only the "basename" BestFile.swift
is logged because the full paths can become very long.
Logging the file name works quite well if all the logs originate from your own code because chances are you will recognise the file name and can easily find it. If however a log message originates from a package you pull in, then this may be a lot harder. Let's say it comes from Utilities.swift
, which package might that be from? Potentially multiple.
This is to say that frequently, it'd be very useful to also know the emitting module but currently in Swift that's not properly supported. SwiftLog currently uses a hack which tries to parse the module name from the full file path which is then uses as the source
parameter for each log message (it uses the last directory name ). As an aside why a source
parameter is useful: In structured logging where you want to preserve metadata across module boundaries, you typically pass through a single Logger
(which holds onto the metadata) to other libraries you call. When passing through the Logger
however we cannot attach the "source" information to the Logger
itself. Instead, the source information needs to be attached to every log message and the current module name seems like a reasonable default.
ADDITION (posted after and thanks to @marco.masser's helpful comment)
Please note that since Swift 5.3, there's another actually documented(!) (in the Literals Expressions section of the Swift Book) workaround to get the current module name by doing string parsing on #fileID
which guarantees that its first path component is the module name:
To parse a
#fileID
expression, read the module name as the text before the first slash (/
) and the filename as the text after the last slash. In the future, the string might contain multiple slashes, such asMyModule/some/disambiguation/MyFile.swift
.
Whilst that works better than SwiftLog's hack, it's still a work around which may incur an allocation and has to do string parsing every time the module name is needed.
Apart from logging, there are numerous other places where it's useful to be able to get the current module name.
Proposed Solution
Much like #filePath
and the others, introduce #module
which hols the current module name. So if a hypothetical module BestModule
did print("I am '\(#module)'")
, it would print I am 'BestModule'
.
Detailed Design
Much like for #fileID
and the others, both of the following uses would work:
let string: String = #module
let staticString: StaticString = #module
ADDITION (posted after the first few comments):
It's crucial that #module
would work like #filePath
and the others and when used as a default value for an argument gets evaluated in the caller's context.
Example: Let's assume we have two modules: App
and Framework
. I the Framework
module we'd have:
public func printCallerModule(_ module: String = #module) {
print(module)
}
And in App
we do
import Framework
printCallerModule()
This should print App
(and not Framework
) because the #module
gets evaluated in the caller's context (ie. the App
module).
Source compatibility
This is an additive change and does not have any material effect on source compatibility.
Effect on ABI stability
This change introduces new conversions at compile-time only, and so would not impact ABI.
Effect on API resilience
This is not an API-level change and would not impact resilience.
Alternatives Considered
We could not add #module
.