Skip to content

Commit a6b83c5

Browse files
committed
logging patterns
1 parent ef8302c commit a6b83c5

12 files changed

Lines changed: 457 additions & 55 deletions

File tree

docs/cli/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ These flags are available on all commands that interact with B2C instances:
3030
- [Sandbox Commands](./sandbox) - Create and manage sandboxes
3131
- [MRT Commands](./mrt) - Manage Managed Runtime environments
3232

33+
## Configuration
34+
35+
- [Logging](./logging) - Log levels, output formats, and environment variables
36+
3337
## Getting Help
3438

3539
Get help for any command:

docs/cli/logging.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Logging
2+
3+
The CLI uses [pino](https://github.com/pinojs/pino) for structured logging with pretty-printed output by default.
4+
5+
## Output Modes
6+
7+
### Pretty Print (Default)
8+
9+
Human-readable output with colors, suitable for interactive terminal use:
10+
11+
```
12+
[18:31:58] INFO: Deploying cartridges...
13+
[18:31:59] INFO: Upload complete
14+
```
15+
16+
### JSON Lines (`--json`)
17+
18+
Machine-readable output for scripting, CI/CD pipelines, and log aggregation:
19+
20+
```bash
21+
b2c code deploy --json
22+
```
23+
24+
```json
25+
{"level":"info","time":1764113529411,"command":"code deploy","msg":"Deploying cartridges..."}
26+
{"level":"info","time":1764113529412,"command":"code deploy","msg":"Upload complete"}
27+
```
28+
29+
## Log Levels
30+
31+
Control verbosity with `--log-level` or `-D` for debug:
32+
33+
| Level | Description |
34+
|-------|-------------|
35+
| `trace` | Maximum verbosity |
36+
| `debug` | Detailed operational info |
37+
| `info` | Normal messages (default) |
38+
| `warn` | Warnings |
39+
| `error` | Errors only |
40+
| `silent` | No output |
41+
42+
```bash
43+
b2c code deploy --log-level debug
44+
b2c code deploy -D # shorthand for --log-level debug
45+
```
46+
47+
In debug/trace mode, context objects are displayed:
48+
49+
```
50+
[18:31:58] INFO: Upload complete
51+
command: "code deploy"
52+
file: "cartridge.zip"
53+
bytes: 45678
54+
duration: 1234
55+
```
56+
57+
## Flags
58+
59+
| Flag | Environment Variable | Description |
60+
|------|---------------------|-------------|
61+
| `--log-level` | `SFCC_LOG_LEVEL` | Set log verbosity |
62+
| `-D, --debug` | `SFCC_DEBUG` | Enable debug logging |
63+
| `--json` | | Output JSON lines |
64+
65+
## Environment Variables
66+
67+
| Variable | Description |
68+
|----------|-------------|
69+
| `SFCC_LOG_LEVEL` | Log level (trace, debug, info, warn, error, silent) |
70+
| `SFCC_DEBUG` | Enable debug logging |
71+
| `SFCC_LOG_TO_STDOUT` | Send logs to stdout instead of stderr |
72+
| `SFCC_LOG_COLORIZE` | Force colors on/off |
73+
| `SFCC_REDACT_SECRETS` | Set to `false` to disable secret redaction |
74+
| `NO_COLOR` | Industry standard to disable colors |
75+
76+
## Output Streams
77+
78+
By default, logs go to **stderr** so that command output (data, IDs, etc.) can be piped cleanly:
79+
80+
```bash
81+
# Logs go to stderr, JSON output goes to stdout
82+
b2c sites list --json 2>/dev/null | jq '.sites[0].id'
83+
```
84+
85+
To send logs to stdout instead:
86+
87+
```bash
88+
SFCC_LOG_TO_STDOUT=1 b2c code deploy
89+
```
90+
91+
## Secret Redaction
92+
93+
Sensitive fields are automatically redacted from log output:
94+
95+
- `password`, `secret`, `token`
96+
- `client_secret`, `access_token`, `refresh_token`
97+
- `api_key`, `authorization`
98+
99+
```
100+
[18:31:58] INFO: Authenticating
101+
client_id: "my-client"
102+
client_secret: "[REDACTED]"
103+
```
104+
105+
To disable redaction (for debugging):
106+
107+
```bash
108+
SFCC_REDACT_SECRETS=false b2c code deploy --debug
109+
```
110+
111+
## CI/CD Usage
112+
113+
For CI/CD pipelines, use JSON output and disable colors:
114+
115+
```bash
116+
NO_COLOR=1 b2c code deploy --json 2>&1 | tee deploy.log
117+
```
118+
119+
Or explicitly set the log level:
120+
121+
```bash
122+
SFCC_LOG_LEVEL=info b2c code deploy --json
123+
```
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {BaseCommand} from '@salesforce/b2c-tooling/cli';
2+
3+
export default class Test extends BaseCommand<typeof Test> {
4+
static description = 'Test logging output';
5+
static hidden = true;
6+
7+
async run(): Promise<void> {
8+
// Test this.log() which now uses pino
9+
this.log('Using this.log() - goes through pino');
10+
this.warn('Using this.warn() - goes through pino');
11+
12+
// Test logger directly at different levels
13+
this.logger.trace('Trace level message');
14+
this.logger.debug('Debug level message');
15+
this.logger.info('Info level message');
16+
this.logger.error('Error level message');
17+
18+
// Context (visible in debug mode)
19+
this.logger.info({operation: 'test', duration: 123}, 'Message with context');
20+
21+
this.logger.debug(
22+
{
23+
file: 'cartridge.zip',
24+
bytes: 45_678,
25+
instance: 'dev01.sandbox.us01.dx.commercecloud.salesforce.com',
26+
},
27+
'Debug with multiple context fields',
28+
);
29+
30+
// Test redaction
31+
this.logger.info(
32+
{
33+
username: 'testuser',
34+
password: 'secret123',
35+
client_secret: 'abc123xyz', // eslint-disable-line camelcase
36+
token: 'Bearer eyJhbGciOiJIUzI1NiJ9.test',
37+
},
38+
'This should have redacted fields',
39+
);
40+
41+
// Child logger
42+
const childLogger = this.logger.child({operation: 'upload'});
43+
childLogger.info('Message from child logger');
44+
}
45+
}

packages/b2c-cli/src/commands/hello/index.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

packages/b2c-cli/src/commands/hello/world.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

packages/b2c-tooling/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
},
149149
"dependencies": {
150150
"i18next": "^25.6.3",
151-
"pino": "^10.1.0"
151+
"pino": "^10.1.0",
152+
"pino-pretty": "^13.1.2"
152153
}
153154
}

packages/b2c-tooling/src/cli/base-command.ts

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ const LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'silent'] as cons
1010

1111
/**
1212
* Base command class for B2C CLI tools.
13+
*
14+
* Environment variables for logging:
15+
* - SFCC_LOG_TO_STDOUT: Send logs to stdout instead of stderr
16+
* - SFCC_LOG_COLORIZE: Force colors on/off (default: auto-detect TTY)
17+
* - SFCC_REDACT_SECRETS: Set to 'false' to disable secret redaction
18+
* - NO_COLOR: Industry standard to disable colors
1319
*/
1420
export abstract class BaseCommand<T extends typeof Command> extends Command {
1521
static baseFlags = {
@@ -26,6 +32,11 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
2632
default: false,
2733
helpGroup: 'LOGGING',
2834
}),
35+
json: Flags.boolean({
36+
description: 'Output logs as JSON lines',
37+
default: false,
38+
helpGroup: 'LOGGING',
39+
}),
2940
lang: Flags.string({
3041
char: 'L',
3142
description: 'Language for messages (e.g., en, de). Also respects LANGUAGE env var.',
@@ -70,6 +81,26 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
7081
this.resolvedConfig = this.loadConfiguration();
7182
}
7283

84+
/**
85+
* Determine colorize setting based on env vars and TTY.
86+
* Priority: NO_COLOR > SFCC_LOG_COLORIZE > TTY detection
87+
*/
88+
private shouldColorize(): boolean {
89+
// NO_COLOR is the industry standard
90+
if (process.env.NO_COLOR !== undefined) {
91+
return false;
92+
}
93+
94+
// Explicit override
95+
const colorizeEnv = process.env.SFCC_LOG_COLORIZE;
96+
if (colorizeEnv !== undefined) {
97+
return colorizeEnv !== 'false' && colorizeEnv !== '0';
98+
}
99+
100+
// Default: colorize if stderr is a TTY
101+
return process.stderr.isTTY ?? false;
102+
}
103+
73104
protected configureLogging(): void {
74105
let level: LogLevel = 'info';
75106

@@ -79,15 +110,42 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
79110
level = 'debug';
80111
}
81112

113+
// Default to stderr (fd 2), allow override to stdout (fd 1)
114+
const fd = process.env.SFCC_LOG_TO_STDOUT ? 1 : 2;
115+
116+
// Redaction: default true, can be disabled
117+
const redact = process.env.SFCC_REDACT_SECRETS !== 'false';
118+
82119
configureLogger({
83120
level,
84-
destination: process.stderr,
121+
fd,
85122
baseContext: {command: this.id},
123+
json: this.flags.json,
124+
colorize: this.shouldColorize(),
125+
redact,
86126
});
87127

88128
this.logger = getLogger();
89129
}
90130

131+
/**
132+
* Override oclif's log() to use pino.
133+
*/
134+
log(message?: string, ...args: unknown[]): void {
135+
if (message !== undefined) {
136+
this.logger.info(args.length > 0 ? `${message} ${args.join(' ')}` : message);
137+
}
138+
}
139+
140+
/**
141+
* Override oclif's warn() to use pino.
142+
*/
143+
warn(input: string | Error): string | Error {
144+
const message = input instanceof Error ? input.message : input;
145+
this.logger.warn(message);
146+
return input;
147+
}
148+
91149
protected loadConfiguration(): ResolvedConfig {
92150
const options: LoadConfigOptions = {
93151
instance: this.flags.instance,

0 commit comments

Comments
 (0)