Watch out! You're reading a series of articles
- (currently reading) Logging Best Practices Series: What to log?
Ready to transform your logs from a wall of gibberish into your most valuable debugging ally?
- Logging Best Practices Series: Logging levels
The Good, The Bad, and The "Why Is This in Production?"
- Logging Best Practices Series: Logging formats
From Chaos to Structure
- Logging Best Practices Series: Contextual logging
Ever tried to debug an issue where a request passes through five different services, ten different methods, and you have no idea which logs belong to which request?
Logging Best Practices for Begginers: How to Not To Lose Your Mind at 3 AM
Picture this: It's 3 AM, you're wrapped in a blanket, clutching your third cup of coffee, and staring at your monitor through bleary eyes. Your phone's still warm from that dreaded emergency call about the payment system being down. Customers are angry, your boss is anxious, and you're digging through logs that look like they were written by a cryptic poet: "Error occurred. Operation failed." Gee, thanks, past me. Really helpful.
Been there? I sure have. And if you haven't yet, trust me - your time will come. This is exactly why we need to talk about logging. Not the boring, "let's-sprinkle-console.log-everywhere-and-call-it-a-day" kind of logging, but the kind that actually saves your bacon when things go sideways.
Look, logging isn't the most exciting part of development - I get it. We'd all rather be building cool features or optimizing that gnarly algorithm. But here's the thing:
good logging is like having a time machine when things go wrong.
It's the difference between a quick fix and an all-night debugging marathon fueled by energy drinks and regret.
This guide isn't about theory or perfect scenarios. It's about real-world, battle-tested logging practices that'll make your future self (and your teammates) actually thank you.
Whether you're building the next big startup or maintaining a legacy system held together by duct tape and prayers, you'll find practical tips you can use right away.
Here's what proper logging gets you:
- The ability to actually understand what went wrong without playing detective
- Early warnings about issues before your users start complaining
- A paper trail for when security asks "who did what and when?"
- Hard data for performance improvements (instead of random guessing)
- Something to show auditors besides your deer-in-headlights expression
Ready to transform your logs from a wall of gibberish into your most valuable debugging ally? Let's dive in and see how to do logging right - your future self will thank you for it.
What to Log (and What Not To)
Our banking integration service started throwing errors at 3 AM (because why not, right?). The logs showed a cryptic "connection refused" message and nothing else. No context, no transaction ID, no customer information - just that lonely error message floating in a sea of logs. After four hours of digging through multiple services and piecing together timestamps manually, we finally found that our service was trying to use an expired OAuth token, but the token refresh logs were nowhere to be found. Fun times!
This is exactly why we need to talk about what to log and what to keep out of our logs. Let's dive in with some real examples.
Essential Events to Log
Application Lifecycle Events
Always log when your app starts up and shuts down. It's basic housekeeping, but you'd be surprised how often this saves the day.
// TypeScript example
class BankingService {
async start() {
logger.info('Banking service starting', {
version: process.env.APP_VERSION,
environment: process.env.NODE_ENV,
config: {
retryAttempts: this.config.retryAttempts,
timeoutMs: this.config.timeoutMs
}
});
// ... startup logic
}
}
// Go example
func main() {
logger := log.With().Str("service", "banking").Logger()
logger.Info().
Str("version", buildVersion).
Str("env", os.Getenv("GO_ENV")).
Interface("config", config).
Msg("Banking service starting")
// ... startup logic
}
Errors and Exceptions
Remember my 3 AM story? Here's how we should have logged that OAuth token error:
try {
await bankApi.makeTransaction(userId, amount);
} catch (error) {
logger.error('Failed to process banking transaction', {
error: error.message,
stackTrace: error.stack,
userId,
transactionId,
tokenExpiry: bankApi.getTokenExpiry(),
// Notice: we're not logging the actual amount or account details!
attemptNumber: retryCount
});
}
State Changes That Matter
Log significant state changes, but be smart about it. For example, when processing a bank transfer:
class TransferService {
async processTransfer(transfer: Transfer) {
logger.info('Starting transfer processing', {
transferId: transfer.id,
sourceType: transfer.sourceType,
destinationType: transfer.destinationType,
// Don't log actual account numbers or amounts!
});
// After validation
logger.info('Transfer validated', {
transferId: transfer.id,
validationDurationMs: validationTimer.elapsed()
});
// After processing
logger.info('Transfer completed', {
transferId: transfer.id,
processingDurationMs: processingTimer.elapsed(),
status: 'completed'
});
}
}
External System Interactions
This is crucial - log your interactions with external services, but be careful with the details:
func (c *Client) callExternalAPI(ctx context.Context, request Request) (*Response, error) {
logger := log.With().
Str("requestId", request.ID).
Str("operation", request.Operation).
Logger()
logger.Debug().
Interface("requestHeaders", request.Headers).
Msg("Calling external API")
startTime := time.Now()
response, err := c.httpClient.Do(request.Build())
duration := time.Since(startTime)
if err != nil {
logger.Error().
Err(err).
Dur("duration_ms", duration).
Msg("External API call failed")
return nil, err
}
logger.Info().
Int("statusCode", response.StatusCode).
Dur("duration_ms", duration).
Msg("External API call completed")
return response, nil
}
What Not to Log (Learn From Our Mistakes)
Sensitive Data: NEVER log:
- Passwords (obviously)
- API keys or tokens
- Account numbers
- Personal data (addresses, phone numbers, etc.)
- Transaction amounts (in financial systems)
Noisy Events: Don't log every heartbeat check or routine operation. I once worked on a service that logged every cache hit - we were generating 2GB of logs per hour just from that!
Instead of:
// Don't do this!
cache.get(key).then(value => {
logger.debug('Cache hit', { key, value }); // Noisy!
});
Do this:
// Much better - only log cache misses as they might indicate issues
cache.get(key).then(value => {
if (!value) {
logger.debug('Cache miss', { key });
}
});
- Redundant Information: If you're using a structured logging system (you should be!), don't repeat information that's already in your metadata:
// Bad
logger.info(`User ${userId} performed action ${action} at timestamp ${new Date().toISOString()}`);
// Good
logger.info('User performed action', {
userId,
action
// timestamp will be automatically added by the logger
});
The Golden Rule
Think of logs as breadcrumbs for your future self (probably at 3 AM, probably after being woken up by a PagerDuty alert). What information would you need to understand and fix the issue quickly? That's what you should log.
Remember my banking integration story? Here's how we fixed our logging:
class BankingIntegration {
async refreshToken() {
logger.info('Starting token refresh', {
currentTokenExpiresIn: this.getTokenExpirySeconds(),
integrationId: this.integrationId
});
try {
const newToken = await this.fetchNewToken();
logger.info('Token refresh successful', {
newTokenExpiresIn: newToken.expiresIn,
integrationId: this.integrationId
});
} catch (error) {
logger.error('Token refresh failed', {
error: error.message,
stackTrace: error.stack,
integrationId: this.integrationId,
lastRefreshAttempt: this.lastRefreshTimestamp
});
throw error;
}
}
}
Now when something goes wrong, we have all the context we need to fix it quickly. And trust me, your future self will thank you for this level of detail!
Watch out! You're reading a series of articles
- (currently reading) Logging Best Practices Series: What to log?
Ready to transform your logs from a wall of gibberish into your most valuable debugging ally?
- Logging Best Practices Series: Logging levels
The Good, The Bad, and The "Why Is This in Production?"
- Logging Best Practices Series: Logging formats
From Chaos to Structure
- Logging Best Practices Series: Contextual logging
Ever tried to debug an issue where a request passes through five different services, ten different methods, and you have no idea which logs belong to which request?