Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 31 additions & 33 deletions Sources/CLTLogger.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import Foundation
#if canImport(WinSDK)
import WinSDK
#endif

import Logging

Expand Down Expand Up @@ -188,8 +191,8 @@ public struct CLTLogger : LogHandler {
}

private static func autoLogStyle(with fh: FileHandle) -> Style {
if let s = getenv("CLTLOGGER_LOG_STYLE") {
switch String(cString: s) {
if let s = ProcessInfo.processInfo.environment["CLTLOGGER_LOG_STYLE"] {
switch s {
case "none": return .none
case "color": return .color
case "emoji": return .emoji
Expand All @@ -200,6 +203,11 @@ public struct CLTLogger : LogHandler {

/* * * The logging style is not defined specifically in the dedicated environment value: we try and detect a correct value depending on other environmental clues. * * */

if ProcessInfo.processInfo.environment["GITHUB_ACTIONS"] == "true" {
/* GitHub does support colors. */
return .color
}
#if !os(Windows)
/* Is the fd on which we write a tty?
* Most ttys nowadays support colors, with a notable exception: Xcode. */
if isatty(fh.fileDescriptor) != 0 {
Expand All @@ -215,12 +223,12 @@ public struct CLTLogger : LogHandler {
}
/* If the TERM env var is not set we assume colors are not supported and return the text logging style.
* In theory we should use the curses database to check for colors (ncurses has the `has_colors` function for this). */
return (getenv("TERM") == nil ? .text : .color)
return (ProcessInfo.processInfo.environment["TERM"] == nil ? .text : .color)
}
if let s = getenv("GITHUB_ACTIONS"), String(cString: s) == "true" {
/* GitHub does support colors. */
return .color
#else
if GetFileType(fh._handle) == FILE_TYPE_CHAR {
}
#endif
/* Unknown case: we return the text logging style. */
return .text
}
Expand Down Expand Up @@ -263,41 +271,31 @@ public extension CLTLogger {
}()

static func defaultConstantsByLogLevelForEmoji(on fh: FileHandle) -> [Logger.Level: Constants] {
func addMeta(_ str: String, _ padding: String) -> Constants {
var str = str
if isatty(fh.fileDescriptor) != 0, tcgetpgrp(fh.fileDescriptor) == -1, errno == ENOTTY {
/* We’re in Xcode (probably).
* By default we do not do the emoji padding, unless explicitly asked to (`CLTLOGGER_TERMINAL_EMOJI` set to anything but “NO”). */
if let s = getenv("CLTLOGGER_TERMINAL_EMOJI"), String(cString: s) != "NO" {
str = str + padding
}
} else {
/* We’re not in Xcode (probably).
* By default we do the emoji padding, unless explicitly asked not to (`CLTLOGGER_TERMINAL_EMOJI` set to “NO”). */
if let s = getenv("CLTLOGGER_TERMINAL_EMOJI"), String(cString: s) == "NO" {
/*nop*/
} else {
str = str + padding
}
}
func addMeta(_ paddedEmoji: String) -> Constants {
return .init(
logPrefix: str + " → ",
multilineLogPrefix: str + " ",
logPrefix: paddedEmoji + " → ",
multilineLogPrefix: paddedEmoji + " ",
metadataLinePrefix: " ▷ ",
metadataSeparator: " - ",
logAndMetadataSeparator: " -- ",
lineSeparator: "\n"
)
}
/* The padding corrects alignment issues on the Terminal. */
let envVars = ProcessInfo.processInfo.environment
let outputEnvironment: OutputEnvironment = .detect(from: fh, envVars)
let emojiSet = EmojiSet.default(for: outputEnvironment)
/* To see all the emojis with the padding. If padding is correct, everything should be aligned. */
//for emoji in Emoji.allCases {
// print("\(emoji.rawValue)\(emoji.padding(for: outputEnvironment)) |")
//}
return [
.trace: addMeta("💩", ""),
.debug: addMeta("⚙️", " "),
.info: addMeta("📔", ""),
.notice: addMeta("🗣", " "),
.warning: addMeta("⚠️", " "),
.error: addMeta("❗️", ""),
.critical: addMeta("‼️", " ")
.trace: addMeta(emojiSet.paddedEmoji(for: .trace, in: outputEnvironment)),
.debug: addMeta(emojiSet.paddedEmoji(for: .debug, in: outputEnvironment)),
.info: addMeta(emojiSet.paddedEmoji(for: .info, in: outputEnvironment)),
.notice: addMeta(emojiSet.paddedEmoji(for: .notice, in: outputEnvironment)),
.warning: addMeta(emojiSet.paddedEmoji(for: .warning, in: outputEnvironment)),
.error: addMeta(emojiSet.paddedEmoji(for: .error, in: outputEnvironment)),
.critical: addMeta(emojiSet.paddedEmoji(for: .critical, in: outputEnvironment)),
]
}

Expand Down
82 changes: 82 additions & 0 deletions Sources/Emoji.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import Foundation



internal enum Emoji : String, CaseIterable {

case poo = "💩"
case cog = "⚙️"
case notebook = "📔"
case speaker = "🗣"
case warning = "⚠️"
case exclamationPoint = "❗️"
case doubleExclamationPoint = "‼️"
case eyebrow = "🤨"
case redCross = "❌"
case policeLight = "🚨"
case worm = "🐛"
case orangeDiamond = "🔶"

case ambulance = "🚑"
case ladybug = "🐞"
case monocle = "🧐"
case greenCheck = "✅"
case fearFace = "😱"

case redHeart = "❤️"
case orangeHeart = "🧡"
case yellowHeart = "💛"
case greenHeart = "💚"
case blueHeart = "💙"
case purpleHeart = "💜"
case blackHeart = "🖤"
case greyHeart = "🩶"
case brownHeart = "🤎"
case whiteHeart = "🤍"
case pinkHeart = "🩷"
case lightBlueHeart = "🩵"

func padding(for environment: OutputEnvironment) -> String {
guard environment != .xcode else {
/* All emojis are correct on Xcode. */
return ""
}

switch self {
case .poo, .notebook, .eyebrow, .redCross, .policeLight, .worm, .orangeDiamond,
.orangeHeart, .yellowHeart, .greenHeart, .blueHeart, .purpleHeart,
.blackHeart, .brownHeart, .whiteHeart:
return ""

case .ambulance, .ladybug, .monocle, .greenCheck, .fearFace:
return ""

case .cog, .warning, .doubleExclamationPoint, .redHeart:
guard !environment.isVSCode, environment != .macOSTerminal
else {return " "}
return ""

case .speaker:
guard !environment.isVSCode, !environment.isWindowsShell, environment != .macOSTerminal, environment != .macOSiTerm2
else {return " "}
return ""

case .exclamationPoint:
/* Note: For the Windows Terminal and Console, we’re a negative 1 space…
# We ignore this special case and return an empty string. */
guard !environment.isWindowsShell
else {return ""/*negative one space*/}
return ""

case .greyHeart, .pinkHeart, .lightBlueHeart:
guard !environment.isVSCode
else {return " "}
return ""
}
}

func valueWithPadding(for environment: OutputEnvironment) -> String {
rawValue + padding(for: environment)
}

}
113 changes: 113 additions & 0 deletions Sources/EmojiSet.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import Foundation

import Logging



internal enum EmojiSet : String {

/**
The original set of emoji used in clt-logger.
These work well in Terminal and Xcode (and on macOS generally, though not in VSCode). */
case original = "ORIGINAL"
case originalForWindowsTerminal = "ORIGINAL+WINDOWS_TERMINAL"
case originalForVSCodeMacOS = "ORIGINAL+VSCODE_MACOS"
case originalForVSCodeWindows = "ORIGINAL+VSCODE_WINDOWS"

case vaibhavsingh97EmojiLogger = "VAIBHAVSINGH97_EMOJI_LOGGER"
case vaibhavsingh97EmojiLoggerForVSCodeMacOS = "VAIBHAVSINGH97_EMOJI_LOGGER+VSCODE_MACOS"

static func `default`(for environment: OutputEnvironment, _ envVars: [String: String] = ProcessInfo.processInfo.environment) -> EmojiSet {
if let envStr = envVars["CLTLOGGER_EMOJI_SET_NAME"], let ret = EmojiSet(rawValue: envStr) {
return ret
}
switch environment {
case .xcode, .macOSTerminal, .macOSiTerm2, .macOSUnknown:
return .original

case .macOSVSCode, .unknownVSCode, .unknown:
return .originalForVSCodeMacOS

case .windowsTerminal, .windowsConsole, .windowsUnknown:
return .originalForWindowsTerminal

case .windowsVSCode:
return .originalForVSCodeWindows
}
}

/* Exceptions:
* - ⚙️ on VSCode macOS renders as text
* - ⚠️ on VSCode macOS renders as text
* - ‼️ on VSCode macOS renders as text
* - ❤️ on VSCode macOS renders as text
* - 🗣 on VSCode Windows renders as text (I think)
* - ‼️ on VSCode Windows renders as text
* - ❗️ on Windows Terminal is larger than the rest (negative padding would be needed)
* - ‼️ on Windows Terminal renders as text */
func emoji(for logLevel: Logger.Level) -> Emoji {
let original: (Logger.Level) -> Emoji = {
switch $0 {
case .critical: return .doubleExclamationPoint
case .error: return .exclamationPoint
case .warning: return .warning
case .notice: return .speaker
case .info: return .notebook
case .debug: return .cog
case .trace: return .poo
}
}
let vaibhavsingh97: (Logger.Level) -> Emoji = {
switch $0 {
case .critical: return .ambulance
case .error: return .fearFace
case .warning: return .warning
case .notice: return .greenCheck /* Called success in upstream. */
case .info: return .monocle
case .debug: return .ladybug
case .trace: return .poo /* Does not exist in upstream. */
}
}

switch self {
case .original:
return original(logLevel)

case .originalForWindowsTerminal:
switch logLevel {
case .critical: return .policeLight
case .error: return .redCross
default: return original(logLevel)
}

case .originalForVSCodeMacOS:
switch logLevel {
case .critical: return .policeLight
case .warning: return .orangeDiamond
case .debug: return .worm
default: return original(logLevel)
}

case .originalForVSCodeWindows:
switch logLevel {
case .critical: return .policeLight
case .notice: return .eyebrow
default: return original(logLevel)
}

case .vaibhavsingh97EmojiLogger:
return vaibhavsingh97(logLevel)

case .vaibhavsingh97EmojiLoggerForVSCodeMacOS:
switch logLevel {
case .warning: return .orangeDiamond
default: return vaibhavsingh97(logLevel)
}
}
}

func paddedEmoji(for logLevel: Logger.Level, in environment: OutputEnvironment) -> String {
return emoji(for: logLevel).valueWithPadding(for: environment)
}

}
88 changes: 88 additions & 0 deletions Sources/OutputEnvironment.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import Foundation



internal enum OutputEnvironment : String {

case xcode = "XCODE"

case macOSTerminal = "MACOS_TERMINAL"
case macOSiTerm2 = "MACOS_ITERM2"
case macOSVSCode = "MACOS_VSCODE"
case macOSUnknown = "MACOS_UNKNOWN"

/* This value is never auto-detected.
* We don’t know how to detect the Windows Terminal (TERM_PROGRAM is not set). */
case windowsTerminal = "WINDOWS_TERMINAL"
/* This value is never auto-detected.
* We don’t know how to detect the Windows Console. */
case windowsConsole = "WINDOWS_CONSOLE"
case windowsVSCode = "WINDOWS_VSCODE"
case windowsUnknown = "WINDOWS_UNKNOWN"

case unknownVSCode = "UNKNOWN_VSCODE"
case unknown = "UNKNOWN"

var isVSCode: Bool {
switch self {
case .macOSVSCode, .windowsVSCode, .unknownVSCode: return true
default: return false
}
}

var isWindowsShell: Bool {
switch self {
case .windowsTerminal, .windowsConsole, .windowsUnknown: return true
default: return false
}
}

static func detect(from fh: FileHandle, _ envVars: [String: String] = ProcessInfo.processInfo.environment) -> OutputEnvironment {
if let envStr = envVars["CLTLOGGER_OUTPUT_ENV"] {
return OutputEnvironment(rawValue: envStr) ?? .unknown
}

#if !os(Windows)
/* Let’s detect Xcode. */
if isatty(fh.fileDescriptor) != 0 && tcgetpgrp(fh.fileDescriptor) == -1 && errno == ENOTTY {
return .xcode
}
#endif
switch envVars["TERM_PROGRAM"] {
case "Apple_Terminal":
#if os(macOS)
return .macOSTerminal
#else
return .unknown
#endif

case "iTerm.app":
#if os(macOS)
return .macOSiTerm2
#else
return .unknown
#endif

case "vscode":
#if os(macOS)
return .macOSVSCode
#elseif os(Windows)
return .windowsVSCode
#else
return .unknownVSCode
#endif

default:
#if os(macOS)
return .macOSUnknown
#elseif os(Windows)
/* We don’t know how to detect the Windows Terminal env:
* anything we have not previously detected on Windows is the Terminal. */
return .windowsTerminal
#else
return .unknown
#endif
}
}

}