or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

asset-management.mdautomation.mdconfiguration.mddynamic-resources.mdindex.mdlogging-diagnostics.mdoutput-system.mdprovider-development.mdresource-management.mdruntime-operations.mdstack-references.mdutilities.md

logging-diagnostics.mddocs/

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