Skip to content

Commit 9d99e36

Browse files
committed
Fix emoji logging on Windows and VSCode
1 parent a064c6d commit 9d99e36

File tree

4 files changed

+274
-43
lines changed

4 files changed

+274
-43
lines changed

Sources/CLTLogger.swift

Lines changed: 17 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -271,58 +271,32 @@ public extension CLTLogger {
271271
}()
272272

273273
static func defaultConstantsByLogLevelForEmoji(on fh: FileHandle) -> [Logger.Level: Constants] {
274-
func addMeta(_ str: String, _ padding: String) -> Constants {
275-
var str = str
276-
#if !os(Windows)
277-
let isXcode = (isatty(fh.fileDescriptor) != 0 && tcgetpgrp(fh.fileDescriptor) == -1 && errno == ENOTTY)
278-
let needsPaddingByDefault = !isXcode
279-
#else
280-
let needsPaddingByDefault = true
281-
#endif
282-
if !needsPaddingByDefault {
283-
/* By default we do not do the emoji padding, unless explicitly asked to (`CLTLOGGER_TERMINAL_EMOJI` set to anything but “NO”). */
284-
if let s = ProcessInfo.processInfo.environment["CLTLOGGER_TERMINAL_EMOJI"], s != "NO" {
285-
str = str + padding
286-
}
287-
} else {
288-
/* By default we do the emoji padding, unless explicitly asked not to (`CLTLOGGER_TERMINAL_EMOJI` set to “NO”). */
289-
if ProcessInfo.processInfo.environment["CLTLOGGER_TERMINAL_EMOJI"] == "NO" {
290-
/*nop*/
291-
} else {
292-
str = str + padding
293-
}
294-
}
274+
func addMeta(_ paddedEmoji: String) -> Constants {
295275
return .init(
296-
logPrefix: str + "",
297-
multilineLogPrefix: str + " ",
276+
logPrefix: paddedEmoji + "",
277+
multilineLogPrefix: paddedEmoji + " ",
298278
metadataLinePrefix: "",
299279
metadataSeparator: " - ",
300280
logAndMetadataSeparator: " -- ",
301281
lineSeparator: "\n"
302282
)
303283
}
304-
/* The padding corrects alignment issues in the Terminal and VSCode.
305-
* In Windows PowerShell, the alignment is off for the debug and warning levels.
306-
* We could try and detect PowerShell somehow and fix the alignment for those. */
284+
let envVars = ProcessInfo.processInfo.environment
285+
let outputEnvironment: OutputEnvironment = .detect(from: fh, envVars)
286+
let emojiSet = EmojiSet.default(for: outputEnvironment)
287+
/* To see all the emojis with the padding. If padding is correct, everything should be aligned. */
288+
//for emoji in Emoji.allCases {
289+
// print("\(emoji.rawValue)\(emoji.padding(for: outputEnvironment)) |")
290+
//}
307291
return [
308-
.trace: addMeta("💩", ""),
309-
.debug: addMeta("⚙️", " "),
310-
.info: addMeta("📔", ""),
311-
.notice: addMeta("🗣", " "),
312-
.warning: addMeta("⚠️", " "),
313-
].merging({
314-
#if !os(Windows)
315-
[
316-
.error: addMeta("❗️", ""),
317-
.critical: addMeta("‼️", " ")
292+
.trace: addMeta(emojiSet.paddedEmoji(for: .trace, in: outputEnvironment)),
293+
.debug: addMeta(emojiSet.paddedEmoji(for: .debug, in: outputEnvironment)),
294+
.info: addMeta(emojiSet.paddedEmoji(for: .info, in: outputEnvironment)),
295+
.notice: addMeta(emojiSet.paddedEmoji(for: .notice, in: outputEnvironment)),
296+
.warning: addMeta(emojiSet.paddedEmoji(for: .warning, in: outputEnvironment)),
297+
.error: addMeta(emojiSet.paddedEmoji(for: .error, in: outputEnvironment)),
298+
.critical: addMeta(emojiSet.paddedEmoji(for: .critical, in: outputEnvironment)),
318299
]
319-
#else
320-
[
321-
.error: addMeta("", ""),
322-
.critical: addMeta("🚨", ""),
323-
]
324-
#endif
325-
}(), uniquingKeysWith: { _, new in preconditionFailure() })
326300
}
327301

328302
/* Terminal does not support RGB colors, so we use 255-color palette. */

Sources/Emoji.swift

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import Foundation
2+
3+
4+
5+
internal enum Emoji : String, CaseIterable {
6+
7+
case poo = "💩"
8+
case cog = "⚙️"
9+
case notebook = "📔"
10+
case speaker = "🗣"
11+
case warning = "⚠️"
12+
case exclamationPoint = "❗️"
13+
case doubleExclamationPoint = "‼️"
14+
case eyebrow = "🤨"
15+
case redCross = ""
16+
case policeLight = "🚨"
17+
case ladybug = "🐞"
18+
case orangeDiamond = "🔶"
19+
20+
case redHeart = "❤️"
21+
case orangeHeart = "🧡"
22+
case yellowHeart = "💛"
23+
case greenHeart = "💚"
24+
case blueHeart = "💙"
25+
case purpleHeart = "💜"
26+
case blackHeart = "🖤"
27+
case greyHeart = "🩶"
28+
case brownHeart = "🤎"
29+
case whiteHeart = "🤍"
30+
case pinkHeart = "🩷"
31+
case lightBlueHeart = "🩵"
32+
33+
func padding(for environment: OutputEnvironment) -> String {
34+
guard environment != .xcode else {
35+
/* All emojis are correct on Xcode. */
36+
return ""
37+
}
38+
39+
switch self {
40+
case .poo, .notebook, .eyebrow, .redCross, .policeLight, .ladybug, .orangeDiamond,
41+
.orangeHeart, .yellowHeart, .greenHeart, .blueHeart, .purpleHeart,
42+
.blackHeart, .brownHeart, .whiteHeart:
43+
return ""
44+
45+
case .cog, .warning, .doubleExclamationPoint, .redHeart:
46+
guard !environment.isVSCode, environment != .macOSTerminal
47+
else {return " "}
48+
return ""
49+
50+
case .speaker:
51+
guard !environment.isVSCode, !environment.isWindowsShell, environment != .macOSTerminal, environment != .macOSiTerm2
52+
else {return " "}
53+
return ""
54+
55+
case .exclamationPoint:
56+
/* Note: For the Windows Terminal and Console, we’re a negative 1 space…
57+
# We ignore this special case and return an empty string. */
58+
guard !environment.isWindowsShell
59+
else {return ""/*negative one space*/}
60+
return ""
61+
62+
case .greyHeart, .pinkHeart, .lightBlueHeart:
63+
guard !environment.isVSCode
64+
else {return " "}
65+
return ""
66+
}
67+
}
68+
69+
func valueWithPadding(for environment: OutputEnvironment) -> String {
70+
rawValue + padding(for: environment)
71+
}
72+
73+
}

Sources/EmojiSet.swift

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import Foundation
2+
3+
import Logging
4+
5+
6+
7+
internal enum EmojiSet : String {
8+
9+
/**
10+
The original set of emoji used in clt-logger.
11+
These work well in Terminal and Xcode (and on macOS generally, though not in VSCode). */
12+
case original = "ORIGINAL"
13+
case originalForWindowsTerminal = "ORIGINAL+WINDOWS_TERMINAL"
14+
case originalForVSCodeMacOS = "ORIGINAL+VSCODE_MACOS"
15+
case originalForVSCodeWindows = "ORIGINAL+VSCODE_WINDOWS"
16+
17+
case vaibhavsingh97EmojiLogger = "VAIBHAVSINGH97_EMOJI_LOGGER"
18+
19+
static func `default`(for environment: OutputEnvironment, _ envVars: [String: String] = ProcessInfo.processInfo.environment) -> EmojiSet {
20+
if let envStr = envVars["CLTLOGGER_EMOJI_SET_NAME"], let ret = EmojiSet(rawValue: envStr) {
21+
return ret
22+
}
23+
switch environment {
24+
case .xcode, .macOSTerminal, .macOSiTerm2, .macOSUnknown:
25+
return .original
26+
27+
case .macOSVSCode, .unknownVSCode, .unknown:
28+
return .originalForVSCodeMacOS
29+
30+
case .windowsTerminal, .windowsConsole, .windowsUnknown:
31+
return .originalForWindowsTerminal
32+
33+
case .windowsVSCode:
34+
return .originalForVSCodeWindows
35+
}
36+
}
37+
38+
/* Exceptions:
39+
* - ⚙️ on VSCode macOS renders as text
40+
* - ⚠️ on VSCode macOS renders as text
41+
* - ‼️ on VSCode macOS renders as text
42+
* - ❤️ on VSCode macOS renders as text
43+
* - 🗣 on VSCode Windows renders as text (I think)
44+
* - ‼️ on VSCode Windows renders as text
45+
* - ❗️ on Windows Terminal is larger than the rest (negative padding would be needed)
46+
* - ‼️ on Windows Terminal renders as text */
47+
func emoji(for logLevel: Logger.Level) -> Emoji {
48+
let original: (Logger.Level) -> Emoji = {
49+
switch $0 {
50+
case .critical: return .doubleExclamationPoint
51+
case .error: return .exclamationPoint
52+
case .warning: return .warning
53+
case .notice: return .speaker
54+
case .info: return .notebook
55+
case .debug: return .cog
56+
case .trace: return .poo
57+
}
58+
}
59+
60+
switch self {
61+
case .original:
62+
return original(logLevel)
63+
64+
case .originalForWindowsTerminal:
65+
switch logLevel {
66+
case .critical: return .policeLight
67+
case .error: return .redCross
68+
default: return original(logLevel)
69+
}
70+
71+
case .originalForVSCodeMacOS:
72+
switch logLevel {
73+
case .critical: return .policeLight
74+
case .warning: return .orangeDiamond
75+
case .debug: return .ladybug
76+
default: return original(logLevel)
77+
}
78+
79+
case .originalForVSCodeWindows:
80+
switch logLevel {
81+
case .critical: return .policeLight
82+
case .notice: return .eyebrow
83+
default: return original(logLevel)
84+
}
85+
86+
case .vaibhavsingh97EmojiLogger:
87+
/* TODO */
88+
return original(logLevel)
89+
}
90+
}
91+
92+
func paddedEmoji(for logLevel: Logger.Level, in environment: OutputEnvironment) -> String {
93+
return emoji(for: logLevel).valueWithPadding(for: environment)
94+
}
95+
96+
}

Sources/OutputEnvironment.swift

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import Foundation
2+
3+
4+
5+
internal enum OutputEnvironment : String {
6+
7+
case xcode = "XCODE"
8+
9+
case macOSTerminal = "MACOS_TERMINAL"
10+
case macOSiTerm2 = "MACOS_ITERM2"
11+
case macOSVSCode = "MACOS_VSCODE"
12+
case macOSUnknown = "MACOS_UNKNOWN"
13+
14+
/* This value is never auto-detected.
15+
* We don’t know how to detect the Windows Terminal (TERM_PROGRAM is not set). */
16+
case windowsTerminal = "WINDOWS_TERMINAL"
17+
/* This value is never auto-detected.
18+
* We don’t know how to detect the Windows Console. */
19+
case windowsConsole = "WINDOWS_CONSOLE"
20+
case windowsVSCode = "WINDOWS_VSCODE"
21+
case windowsUnknown = "WINDOWS_UNKNOWN"
22+
23+
case unknownVSCode = "UNKNOWN_VSCODE"
24+
case unknown = "UNKNOWN"
25+
26+
var isVSCode: Bool {
27+
switch self {
28+
case .macOSVSCode, .windowsVSCode, .unknownVSCode: return true
29+
default: return false
30+
}
31+
}
32+
33+
var isWindowsShell: Bool {
34+
switch self {
35+
case .windowsTerminal, .windowsConsole, .windowsUnknown: return true
36+
default: return false
37+
}
38+
}
39+
40+
static func detect(from fh: FileHandle, _ envVars: [String: String] = ProcessInfo.processInfo.environment) -> OutputEnvironment {
41+
if let envStr = envVars["CLTLOGGER_OUTPUT_ENV"] {
42+
return OutputEnvironment(rawValue: envStr) ?? .unknown
43+
}
44+
45+
#if !os(Windows)
46+
/* Let’s detect Xcode. */
47+
if isatty(fh.fileDescriptor) != 0 && tcgetpgrp(fh.fileDescriptor) == -1 && errno == ENOTTY {
48+
return .xcode
49+
}
50+
#endif
51+
switch envVars["TERM_PROGRAM"] {
52+
case "Apple_Terminal":
53+
#if os(macOS)
54+
return .macOSTerminal
55+
#else
56+
return .unknown
57+
#endif
58+
59+
case "iTerm.app":
60+
#if os(macOS)
61+
return .macOSiTerm2
62+
#else
63+
return .unknown
64+
#endif
65+
66+
case "vscode":
67+
#if os(macOS)
68+
return .macOSVSCode
69+
#elseif os(Windows)
70+
return .windowsVSCode
71+
#else
72+
return .unknownVSCode
73+
#endif
74+
75+
default:
76+
#if os(macOS)
77+
return .macOSUnknown
78+
#elseif os(Windows)
79+
/* We don’t know how to detect the Windows Terminal env:
80+
* anything we have not previously detected on Windows is the Terminal. */
81+
return .windowsTerminal
82+
#else
83+
return .unknown
84+
#endif
85+
}
86+
}
87+
88+
}

0 commit comments

Comments
 (0)