
Why Add Logging to Your Astro Site?
Modern web applications need robust logging for monitoring, debugging, and security. While Vercel provides excellent analytics and error tracking, adding Axiom gives you deeper insights into your application's behavior, especially for API endpoints and custom events.
In this guide, we'll walk through implementing Axiom logging in an Astro site, covering both client-side and server-side logging. We'll focus on practical implementation with real code examples.
Prerequisites
Before we begin, you'll need:
- An existing Astro project
- An Axiom account (free tier available)
- Basic understanding of TypeScript
Initial Setup
First, let's set up our environment variables. Create or update your .env
file:
# Axiom Configuration
AXIOM_TOKEN=your-token-here
AXIOM_DATASET=your-dataset-name
AXIOM_ORG_ID=your-org-id
Make sure to add these variables to your deployment platform (e.g., Vercel) as well.
Creating the Logger Class
Let's create a TypeScript logger class that handles our logging needs. Create a new file at src/utils/logger.ts
:
import fetch from 'node-fetch';
// Define log levels
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
// Define log data structure
interface LogData {
message: string;
level: LogLevel;
context?: Record<string, any>;
timestamp?: string;
[key: string]: any;
}
class Logger {
private static instance: Logger;
private readonly dataset: string;
private readonly token: string;
private readonly isDevelopment: boolean;
private constructor() {
this.dataset = import.meta.env.AXIOM_DATASET || 'your-dataset';
this.token = import.meta.env.AXIOM_TOKEN || '';
this.isDevelopment = import.meta.env.DEV === true;
}
public static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
private async ingest(data: LogData) {
try {
// Always add timestamp if not provided
const logEntry = {
...data,
timestamp: data.timestamp || new Date().toISOString(),
environment: this.isDevelopment ? 'development' : 'production'
};
// In development, also log to console
if (this.isDevelopment) {
console[data.level](logEntry);
}
// Send to Axiom
const response = await fetch('https://api.axiom.co/v1/datasets/' + this.dataset + '/ingest', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + this.token,
'Content-Type': 'application/json',
},
body: JSON.stringify([logEntry])
});
if (!response.ok) {
throw new Error('Failed to send log to Axiom: ' + response.statusText);
}
} catch (error) {
console.error('Logging failed:', error);
console.error('Original log:', data);
}
}
public async log(level: LogLevel, message: string, context?: Record<string, any>) {
await this.ingest({
level,
message,
context,
service: 'api'
});
}
public async debug(message: string, context?: Record<string, any>) {
await this.log('debug', message, context);
}
public async info(message: string, context?: Record<string, any>) {
await this.log('info', message, context);
}
public async warn(message: string, context?: Record<string, any>) {
await this.log('warn', message, context);
}
public async error(message: string, context?: Record<string, any>) {
await this.log('error', message, context);
}
}
Adding Client-Side Logging
For client-side logging, including Web Vitals tracking, add this script to your src/layouts/partials/HeaderScripts.astro
:
<!-- Axiom Analytics -->
<script is:inline define:vars={{ dataset: import.meta.env.AXIOM_DATASET, token: import.meta.env.AXIOM_TOKEN }}>
// Initialize Axiom logging
window.axiom = {
dataset,
token,
webVitals: true
};
// Web Vitals reporting
function sendToAxiom({ name, delta, id }) {
const body = {
timestamp: new Date().toISOString(),
name,
value: delta,
id,
page: window.location.pathname,
};
fetch('https://api.axiom.co/v1/datasets/' + dataset + '/ingest', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json',
},
body: JSON.stringify([body])
}).catch(console.error);
}
// Report Web Vitals
if (window.webVitals) {
window.webVitals.getCLS(sendToAxiom);
window.webVitals.getFID(sendToAxiom);
window.webVitals.getLCP(sendToAxiom);
window.webVitals.getFCP(sendToAxiom);
window.webVitals.getTTFB(sendToAxiom);
}
</script>
<!-- Load Web Vitals library -->
<script defer src="https://unpkg.com/web-vitals"></script>
Implementing API Logging
For API endpoints, use the logger in your API routes. Here's an example:
import type { APIRoute } from 'astro';
import { logger } from '../utils/logger';
export const post: APIRoute = async ({ request, clientAddress }) => {
try {
// Log incoming request
await logger.info('API Request', {
path: new URL(request.url).pathname,
method: request.method,
clientAddress
});
// Your API logic here
const data = await request.json();
// Log successful response
await logger.info('API Response', {
success: true,
data
});
return new Response(JSON.stringify({ success: true }), {
status: 200
});
} catch (error) {
// Log error
await logger.error('API Error', {
error: error instanceof Error ? error.message : 'Unknown error',
stack: error instanceof Error ? error.stack : undefined
});
return new Response(JSON.stringify({ success: false }), {
status: 500
});
}
};
Viewing Your Logs
To view your logs:
- Log into your Axiom dashboard
- Select your dataset
- Use the Stream view for real-time logs
- Use the Query interface for detailed analysis
Common queries you might want to try:
level:"error"
- View all errorsenvironment:"production"
- Production logs onlyname:"CLS"
- Core Web Vitals measurements
Best Practices
- Environment Separation: Always tag logs with the environment (development/production)
- Contextual Information: Include relevant context with each log
- Security: Never log sensitive information
- Performance: Use batch logging when possible
- Error Handling: Always include error stacks and context
Wrapping Up
With Axiom logging implemented, you now have comprehensive insights into your Astro application's behavior. You can track performance metrics, monitor API endpoints, and quickly identify issues in both development and production environments.
Remember to regularly review your logs and set up alerts for critical events to maintain a healthy application.