1
1
import Foundation
2
- #if canImport(System)
3
- import System
4
- #else
5
- import SystemPackage
6
- #endif
7
2
8
3
import Logging
9
4
@@ -18,7 +13,7 @@ import Logging
18
13
+ Ouptuts to stderr by default.
19
14
The idea is: “usable” text (text that is actually what the user asked for when launching your tool) should be output to stdout,
20
15
presumably using `print`, the rest should be on stderr.
21
- If needed you can setup the logger to use any fd , the logger will simply `write(2)` to it.
16
+ If needed you can setup the logger to use any file descriptor (via a FileHandle) , the logger will simply `write(2)` to it.
22
17
+ Ouptut has special control chars for colors if the output fd is a tty and Xcode is not detected.
23
18
You can force using or force not using colors.
24
19
+ If the write syscall fails, the log is lost (or partially lost; interrupts are retried; see SystemPackage for more info).
@@ -130,27 +125,27 @@ public struct CLTLogger : LogHandler {
130
125
}
131
126
public var metadataProvider : Logger . MetadataProvider ?
132
127
133
- public let outputFileDescriptor : FileDescriptor
128
+ public let outputFileHandle : FileHandle
134
129
public let multilineMode : MultilineMode
135
130
public let constantsByLevel : [ Logger . Level : Constants ]
136
131
137
- public init ( fd : FileDescriptor = . standardError, multilineMode: MultilineMode = . default, logStyle: Style = . auto, metadataProvider: Logger . MetadataProvider ? = LoggingSystem . metadataProvider) {
138
- let logPrefixStyle = ( logStyle != . auto ? logStyle : CLTLogger . autoLogStyle ( with: fd ) )
132
+ public init ( fileHandle : FileHandle = . standardError, multilineMode: MultilineMode = . default, logStyle: Style = . auto, metadataProvider: Logger . MetadataProvider ? = LoggingSystem . metadataProvider) {
133
+ let logPrefixStyle = ( logStyle != . auto ? logStyle : CLTLogger . autoLogStyle ( with: fileHandle ) )
139
134
140
135
let constantsByLevel : [ Logger . Level : Constants ]
141
136
switch logPrefixStyle {
142
137
case . none: constantsByLevel = [ : ]
143
138
case . text: constantsByLevel = CLTLogger . defaultConstantsByLogLevelForText
144
- case . emoji: constantsByLevel = CLTLogger . defaultConstantsByLogLevelForEmoji ( on: fd )
139
+ case . emoji: constantsByLevel = CLTLogger . defaultConstantsByLogLevelForEmoji ( on: fileHandle )
145
140
case . color: constantsByLevel = CLTLogger . defaultConstantsByLogLevelForColors
146
141
case . auto: fatalError ( )
147
142
}
148
143
149
- self . init ( fd : fd , multilineMode: multilineMode, constantsByLevel: constantsByLevel, metadataProvider: metadataProvider)
144
+ self . init ( fileHandle : fileHandle , multilineMode: multilineMode, constantsByLevel: constantsByLevel, metadataProvider: metadataProvider)
150
145
}
151
146
152
- public init ( fd : FileDescriptor = . standardError, multilineMode: MultilineMode = . default, constantsByLevel: [ Logger . Level : Constants ] , metadataProvider: Logger . MetadataProvider ? = LoggingSystem . metadataProvider) {
153
- self . outputFileDescriptor = fd
147
+ public init ( fileHandle : FileHandle = . standardError, multilineMode: MultilineMode = . default, constantsByLevel: [ Logger . Level : Constants ] , metadataProvider: Logger . MetadataProvider ? = LoggingSystem . metadataProvider) {
148
+ self . outputFileHandle = fileHandle
154
149
self . multilineMode = multilineMode
155
150
self . constantsByLevel = constantsByLevel
156
151
@@ -174,22 +169,25 @@ public struct CLTLogger : LogHandler {
174
169
/* We compute the data to print outside of the lock. */
175
170
let data = Self . format ( message: message. description, flatMetadata: effectiveFlatMetadata, multilineMode: multilineMode, constants: constants)
176
171
177
- Self . write ( data, to: outputFileDescriptor )
172
+ Self . write ( data, to: outputFileHandle )
178
173
}
179
174
180
175
/** Writes to the given file descriptor like the logger would. */
181
- public static func write( _ data: Data , to fd : FileDescriptor ) {
176
+ public static func write( _ data: Data , to fh : FileHandle ) {
182
177
/* We lock, because the writeAll function might split the write in more than 1 write
183
178
* (if the write system call only writes a part of the data).
184
- * If another part of the program writes to fd , we might get interleaved data,
179
+ * If another part of the program writes to the file descriptor , we might get interleaved data,
185
180
* because they cannot be aware of our lock (and we cannot be aware of theirs if they have one). */
186
181
CLTLogger . lock. withLock {
187
182
/* Is there a better idea than silently drop the message in case of fail? */
188
- _ = try ? fd. writeAll ( data)
183
+ /* Is the write retried on interrupt?
184
+ * We’ll assume yes, but we don’t and can’t know for sure
185
+ * until FileHandle has been migrated to the open-source Foundation. */
186
+ _ = try ? fh. write ( contentsOf: data)
189
187
}
190
188
}
191
189
192
- private static func autoLogStyle( with fd : FileDescriptor ) -> Style {
190
+ private static func autoLogStyle( with fh : FileHandle ) -> Style {
193
191
if let s = getenv ( " CLTLOGGER_LOG_STYLE " ) {
194
192
switch String ( cString: s) {
195
193
case " none " : return . none
@@ -204,14 +202,14 @@ public struct CLTLogger : LogHandler {
204
202
205
203
/* Is the fd on which we write a tty?
206
204
* Most ttys nowadays support colors, with a notable exception: Xcode. */
207
- if isatty ( fd . rawValue ) != 0 {
205
+ if isatty ( fh . fileDescriptor ) != 0 {
208
206
/* Xcode detection: it ain’t trivial.
209
207
* I found checking for the existence of the __XCODE_BUILT_PRODUCTS_DIR_PATHS env var to be a possible solution.
210
208
* We could also probably check for the existence of the TERM env var: Xcode does not set it.
211
209
* (When Package.swift is built we can check if the value of the __CFBundleIdentifier env var is "com.apple.dt.Xcode".)
212
210
* The solution we’re currently using is to check whether the fd on which we write has a foreground process group as Xcode does not set one.
213
211
* Note: If Xcode detection is changed here, it should also be changed in defaultConstantsByLogLevelForEmoji. */
214
- if tcgetpgrp ( fd . rawValue ) == - 1 && errno == ENOTTY {
212
+ if tcgetpgrp ( fh . fileDescriptor ) == - 1 && errno == ENOTTY {
215
213
/* We log using emojis in Xcode. */
216
214
return . emoji
217
215
}
@@ -264,10 +262,10 @@ public extension CLTLogger {
264
262
]
265
263
} ( )
266
264
267
- static func defaultConstantsByLogLevelForEmoji( on fd : FileDescriptor ) -> [ Logger . Level : Constants ] {
265
+ static func defaultConstantsByLogLevelForEmoji( on fh : FileHandle ) -> [ Logger . Level : Constants ] {
268
266
func addMeta( _ str: String , _ padding: String ) -> Constants {
269
267
var str = str
270
- if isatty ( fd . rawValue ) != 0 , tcgetpgrp ( fd . rawValue ) == - 1 , errno == ENOTTY {
268
+ if isatty ( fh . fileDescriptor ) != 0 , tcgetpgrp ( fh . fileDescriptor ) == - 1 , errno == ENOTTY {
271
269
/* We’re in Xcode (probably).
272
270
* By default we do not do the emoji padding, unless explicitly asked to (`CLTLOGGER_TERMINAL_EMOJI` set to anything but “NO”). */
273
271
if let s = getenv ( " CLTLOGGER_TERMINAL_EMOJI " ) , String ( cString: s) != " NO " {
0 commit comments