0
# Logging and Diagnostics
1
2
Pulumi's logging system provides structured logging and error handling for infrastructure deployment diagnostics, enabling better observability and debugging of infrastructure operations.
3
4
## Logging Functions
5
6
```typescript { .api }
7
function hasErrors(): boolean;
8
function debug(msg: string, resource?: Resource, streamId?: number, ephemeral?: boolean): void;
9
function info(msg: string, resource?: Resource, streamId?: number, ephemeral?: boolean): void;
10
function warn(msg: string, resource?: Resource, streamId?: number, ephemeral?: boolean): void;
11
function error(msg: string, resource?: Resource, streamId?: number, ephemeral?: boolean): void;
12
```
13
14
## Error Classes
15
16
```typescript { .api }
17
class RunError extends Error {
18
readonly __pulumiRunError: boolean;
19
static isInstance(obj: any): obj is RunError;
20
}
21
22
class ResourceError extends Error {
23
readonly __pulumResourceError: boolean;
24
resource: Resource | undefined;
25
hideStack?: boolean;
26
27
constructor(message: string, resource: Resource | undefined, hideStack?: boolean);
28
static isInstance(obj: any): obj is ResourceError;
29
}
30
31
class InputPropertyError extends Error {
32
readonly __pulumiInputPropertyError: boolean;
33
propertyPath: string;
34
reason: string;
35
36
constructor(args: InputPropertyErrorDetails);
37
static isInstance(obj: any): obj is InputPropertyError;
38
}
39
40
class InputPropertiesError extends Error {
41
readonly __pulumiInputPropertiesError: boolean;
42
reasons: InputPropertyError[];
43
44
constructor(errors: InputPropertyError[]);
45
static isInstance(obj: any): obj is InputPropertiesError;
46
}
47
48
interface InputPropertyErrorDetails {
49
propertyPath: string;
50
reason: string;
51
}
52
53
function isGrpcError(err: Error): boolean;
54
```
55
56
## Usage Examples
57
58
### Basic Logging
59
60
```typescript
61
import * as pulumi from "@pulumi/pulumi";
62
63
// Simple logging without resource context
64
pulumi.log.info("Starting infrastructure deployment");
65
pulumi.log.debug("Configuration loaded successfully");
66
pulumi.log.warn("Using default values for missing configuration");
67
68
// Check for errors
69
if (pulumi.log.hasErrors()) {
70
pulumi.log.error("Deployment failed due to previous errors");
71
process.exit(1);
72
}
73
```
74
75
### Resource-Contextual Logging
76
77
```typescript
78
import * as pulumi from "@pulumi/pulumi";
79
import * as aws from "@pulumi/aws";
80
81
const bucket = new aws.s3.Bucket("my-bucket", {
82
acl: "private",
83
});
84
85
// Log with resource context
86
pulumi.log.info("Creating S3 bucket with private ACL", bucket);
87
pulumi.log.debug(`Bucket configuration: ${JSON.stringify({
88
acl: "private",
89
versioning: false,
90
})}`, bucket);
91
92
bucket.id.apply(id => {
93
pulumi.log.info(`S3 bucket created with ID: ${id}`, bucket);
94
});
95
96
// Warning with resource context
97
if (!bucket.versioning) {
98
pulumi.log.warn("Bucket versioning is disabled - consider enabling for production", bucket);
99
}
100
```
101
102
### Error Handling
103
104
```typescript
105
import * as pulumi from "@pulumi/pulumi";
106
107
// Throw RunError for clean program termination
108
if (!process.env.AWS_REGION) {
109
throw new pulumi.RunError("AWS_REGION environment variable is required");
110
}
111
112
// Throw ResourceError for resource-specific issues
113
const database = new aws.rds.Instance("prod-db", {
114
engine: "postgres",
115
instanceClass: "db.t3.micro",
116
});
117
118
// Validate resource configuration
119
database.engine.apply(engine => {
120
if (engine !== "postgres" && engine !== "mysql") {
121
throw new pulumi.ResourceError(
122
`Unsupported database engine: ${engine}`,
123
database,
124
false // Don't hide stack trace
125
);
126
}
127
});
128
```
129
130
### Input Validation Errors
131
132
```typescript
133
import * as pulumi from "@pulumi/pulumi";
134
135
function validateBucketName(name: string): void {
136
const errors: pulumi.InputPropertyError[] = [];
137
138
if (!name) {
139
errors.push(new pulumi.InputPropertyError({
140
propertyPath: "bucketName",
141
reason: "bucketName is required"
142
}));
143
}
144
145
if (name && name.length < 3) {
146
errors.push(new pulumi.InputPropertyError({
147
propertyPath: "bucketName",
148
reason: "bucketName must be at least 3 characters long"
149
}));
150
}
151
152
if (name && !/^[a-z0-9.-]+$/.test(name)) {
153
errors.push(new pulumi.InputPropertyError({
154
propertyPath: "bucketName",
155
reason: "bucketName can only contain lowercase letters, numbers, periods, and hyphens"
156
}));
157
}
158
159
if (errors.length > 0) {
160
throw new pulumi.InputPropertiesError(errors);
161
}
162
}
163
164
// Use validation
165
try {
166
validateBucketName("MyBucket"); // Will throw error
167
} catch (error) {
168
if (pulumi.InputPropertiesError.isInstance(error)) {
169
error.reasons.forEach(reason => {
170
pulumi.log.error(`Validation failed for ${reason.propertyPath}: ${reason.reason}`);
171
});
172
}
173
}
174
```
175
176
### Structured Logging Patterns
177
178
```typescript
179
import * as pulumi from "@pulumi/pulumi";
180
181
class InfrastructureLogger {
182
private context: string;
183
184
constructor(context: string) {
185
this.context = context;
186
}
187
188
logOperation(operation: string, resource?: pulumi.Resource): void {
189
pulumi.log.info(`[${this.context}] ${operation}`, resource);
190
}
191
192
logConfig(config: any, resource?: pulumi.Resource): void {
193
pulumi.log.debug(`[${this.context}] Configuration: ${JSON.stringify(config)}`, resource);
194
}
195
196
logWarning(message: string, resource?: pulumi.Resource): void {
197
pulumi.log.warn(`[${this.context}] ${message}`, resource);
198
}
199
200
logError(message: string, error?: Error, resource?: pulumi.Resource): void {
201
const errorMsg = error ? `${message}: ${error.message}` : message;
202
pulumi.log.error(`[${this.context}] ${errorMsg}`, resource);
203
}
204
}
205
206
// Usage
207
const logger = new InfrastructureLogger("WebApp");
208
209
const bucket = new aws.s3.Bucket("web-assets", {
210
acl: "public-read",
211
});
212
213
logger.logOperation("Creating S3 bucket for web assets", bucket);
214
logger.logConfig({ acl: "public-read", versioning: false }, bucket);
215
216
bucket.id.apply(id => {
217
logger.logOperation(`S3 bucket created: ${id}`, bucket);
218
});
219
```
220
221
### Conditional Logging
222
223
```typescript
224
import * as pulumi from "@pulumi/pulumi";
225
226
const config = new pulumi.Config();
227
const debugMode = config.getBoolean("debug") || false;
228
const environment = config.get("environment") || "development";
229
230
function conditionalLog(level: "debug" | "info" | "warn" | "error", message: string, resource?: pulumi.Resource): void {
231
// Only log debug messages in debug mode
232
if (level === "debug" && !debugMode) {
233
return;
234
}
235
236
// Add environment prefix in production
237
const prefixedMessage = environment === "production" ? `[PROD] ${message}` : message;
238
239
switch (level) {
240
case "debug":
241
pulumi.log.debug(prefixedMessage, resource);
242
break;
243
case "info":
244
pulumi.log.info(prefixedMessage, resource);
245
break;
246
case "warn":
247
pulumi.log.warn(prefixedMessage, resource);
248
break;
249
case "error":
250
pulumi.log.error(prefixedMessage, resource);
251
break;
252
}
253
}
254
255
// Usage
256
conditionalLog("debug", "Detailed configuration loaded");
257
conditionalLog("info", "Starting deployment");
258
conditionalLog("warn", "Using development configuration in production");
259
```
260
261
### Error Recovery Patterns
262
263
```typescript
264
import * as pulumi from "@pulumi/pulumi";
265
266
async function createResourceWithRetry<T extends pulumi.Resource>(
267
createFn: () => T,
268
maxRetries: number = 3
269
): Promise<T> {
270
let lastError: Error | undefined;
271
272
for (let attempt = 1; attempt <= maxRetries; attempt++) {
273
try {
274
pulumi.log.debug(`Attempt ${attempt}/${maxRetries} to create resource`);
275
return createFn();
276
} catch (error) {
277
lastError = error as Error;
278
pulumi.log.warn(`Attempt ${attempt} failed: ${error.message}`);
279
280
if (attempt === maxRetries) {
281
pulumi.log.error(`All ${maxRetries} attempts failed`);
282
break;
283
}
284
285
// Wait before retry
286
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
287
}
288
}
289
290
throw new pulumi.RunError(`Failed to create resource after ${maxRetries} attempts: ${lastError?.message}`);
291
}
292
293
// Usage
294
const bucket = await createResourceWithRetry(() => new aws.s3.Bucket("retry-bucket", {
295
acl: "private",
296
}));
297
```
298
299
### Diagnostic Information
300
301
```typescript
302
import * as pulumi from "@pulumi/pulumi";
303
304
function logDiagnosticInfo(): void {
305
pulumi.log.info(`Project: ${pulumi.getProject()}`);
306
pulumi.log.info(`Stack: ${pulumi.getStack()}`);
307
pulumi.log.info(`Organization: ${pulumi.getOrganization()}`);
308
309
const config = new pulumi.Config();
310
const allConfig = Object.keys(process.env)
311
.filter(key => key.startsWith('PULUMI_CONFIG_'))
312
.reduce((acc, key) => {
313
const configKey = key.replace('PULUMI_CONFIG_', '').toLowerCase();
314
acc[configKey] = config.get(configKey) || '<not set>';
315
return acc;
316
}, {} as Record<string, string>);
317
318
pulumi.log.debug(`Configuration: ${JSON.stringify(allConfig)}`);
319
}
320
321
// Log diagnostic info at startup
322
logDiagnosticInfo();
323
```
324
325
### Performance Logging
326
327
```typescript
328
import * as pulumi from "@pulumi/pulumi";
329
330
class PerformanceLogger {
331
private timers: Map<string, number> = new Map();
332
333
startTimer(label: string): void {
334
this.timers.set(label, Date.now());
335
pulumi.log.debug(`Started timer: ${label}`);
336
}
337
338
endTimer(label: string, resource?: pulumi.Resource): void {
339
const startTime = this.timers.get(label);
340
if (startTime) {
341
const duration = Date.now() - startTime;
342
pulumi.log.info(`${label} completed in ${duration}ms`, resource);
343
this.timers.delete(label);
344
}
345
}
346
}
347
348
const perfLogger = new PerformanceLogger();
349
350
perfLogger.startTimer("S3 Bucket Creation");
351
const bucket = new aws.s3.Bucket("perf-bucket");
352
bucket.id.apply(() => {
353
perfLogger.endTimer("S3 Bucket Creation", bucket);
354
});
355
```
356
357
## Best Practices
358
359
- Use appropriate log levels (debug for detailed info, info for normal operations, warn for potential issues, error for failures)
360
- Include resource context in logs when possible for better debugging
361
- Use structured logging patterns for consistency
362
- Implement conditional logging based on environment or debug flags
363
- Handle errors gracefully with proper error types
364
- Log performance metrics for long-running operations
365
- Include diagnostic information in deployment logs
366
- Use InputPropertyError and InputPropertiesError for validation failures
367
- Avoid logging sensitive information (passwords, API keys)
368
- Implement retry logic with appropriate logging
369
- Use ephemeral logging for temporary debug information
370
- Log configuration validation results
371
- Include stack traces for debugging when appropriate