POST /api/logs
curl --request POST \
--url https://vij.example.com/api/logs \
--header 'Content-Type: application/json' \
--data '
{
"message": "<string>",
"name": "<string>",
"stack": "<string>",
"severity": "<string>",
"timestamp": "<string>",
"appId": "<string>",
"environment": "<string>",
"metadata": {},
"context": {}
}
'import requests
url = "https://vij.example.com/api/logs"
payload = {
"message": "<string>",
"name": "<string>",
"stack": "<string>",
"severity": "<string>",
"timestamp": "<string>",
"appId": "<string>",
"environment": "<string>",
"metadata": {},
"context": {}
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
message: '<string>',
name: '<string>',
stack: '<string>',
severity: '<string>',
timestamp: '<string>',
appId: '<string>',
environment: '<string>',
metadata: {},
context: {}
})
};
fetch('https://vij.example.com/api/logs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://vij.example.com/api/logs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'message' => '<string>',
'name' => '<string>',
'stack' => '<string>',
'severity' => '<string>',
'timestamp' => '<string>',
'appId' => '<string>',
'environment' => '<string>',
'metadata' => [
],
'context' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://vij.example.com/api/logs"
payload := strings.NewReader("{\n \"message\": \"<string>\",\n \"name\": \"<string>\",\n \"stack\": \"<string>\",\n \"severity\": \"<string>\",\n \"timestamp\": \"<string>\",\n \"appId\": \"<string>\",\n \"environment\": \"<string>\",\n \"metadata\": {},\n \"context\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://vij.example.com/api/logs")
.header("Content-Type", "application/json")
.body("{\n \"message\": \"<string>\",\n \"name\": \"<string>\",\n \"stack\": \"<string>\",\n \"severity\": \"<string>\",\n \"timestamp\": \"<string>\",\n \"appId\": \"<string>\",\n \"environment\": \"<string>\",\n \"metadata\": {},\n \"context\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://vij.example.com/api/logs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"message\": \"<string>\",\n \"name\": \"<string>\",\n \"stack\": \"<string>\",\n \"severity\": \"<string>\",\n \"timestamp\": \"<string>\",\n \"appId\": \"<string>\",\n \"environment\": \"<string>\",\n \"metadata\": {},\n \"context\": {}\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"id": "65a1b2c3d4e5f6g7h8i9j0k1",
"group": "abc123def456"
}
Ingestion API
POST /api/logs
Ingest error logs into VIJ Admin
POST
/
api
/
logs
POST /api/logs
curl --request POST \
--url https://vij.example.com/api/logs \
--header 'Content-Type: application/json' \
--data '
{
"message": "<string>",
"name": "<string>",
"stack": "<string>",
"severity": "<string>",
"timestamp": "<string>",
"appId": "<string>",
"environment": "<string>",
"metadata": {},
"context": {}
}
'import requests
url = "https://vij.example.com/api/logs"
payload = {
"message": "<string>",
"name": "<string>",
"stack": "<string>",
"severity": "<string>",
"timestamp": "<string>",
"appId": "<string>",
"environment": "<string>",
"metadata": {},
"context": {}
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
message: '<string>',
name: '<string>',
stack: '<string>',
severity: '<string>',
timestamp: '<string>',
appId: '<string>',
environment: '<string>',
metadata: {},
context: {}
})
};
fetch('https://vij.example.com/api/logs', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://vij.example.com/api/logs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'message' => '<string>',
'name' => '<string>',
'stack' => '<string>',
'severity' => '<string>',
'timestamp' => '<string>',
'appId' => '<string>',
'environment' => '<string>',
'metadata' => [
],
'context' => [
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://vij.example.com/api/logs"
payload := strings.NewReader("{\n \"message\": \"<string>\",\n \"name\": \"<string>\",\n \"stack\": \"<string>\",\n \"severity\": \"<string>\",\n \"timestamp\": \"<string>\",\n \"appId\": \"<string>\",\n \"environment\": \"<string>\",\n \"metadata\": {},\n \"context\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://vij.example.com/api/logs")
.header("Content-Type", "application/json")
.body("{\n \"message\": \"<string>\",\n \"name\": \"<string>\",\n \"stack\": \"<string>\",\n \"severity\": \"<string>\",\n \"timestamp\": \"<string>\",\n \"appId\": \"<string>\",\n \"environment\": \"<string>\",\n \"metadata\": {},\n \"context\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://vij.example.com/api/logs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"message\": \"<string>\",\n \"name\": \"<string>\",\n \"stack\": \"<string>\",\n \"severity\": \"<string>\",\n \"timestamp\": \"<string>\",\n \"appId\": \"<string>\",\n \"environment\": \"<string>\",\n \"metadata\": {},\n \"context\": {}\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"id": "65a1b2c3d4e5f6g7h8i9j0k1",
"group": "abc123def456"
}
The POST
Recommended limits:
Custom CORS (if needed):
Response:
/api/logs endpoint receives error logs from vij-sdk or custom clients and stores them in MongoDB.
Request
string
required
The error message describing what went wrong.Example:
"TypeError: Cannot read property 'map' of undefined"string
required
The error name or type.Example:
"TypeError", "ReferenceError", "CustomError"string
required
Full stack trace of the error.Example:
Error: Payment processing failed
at processPayment (payment.js:45:12)
at handleCheckout (checkout.js:123:5)
at onClick (Button.tsx:67:20)
string
required
Severity level of the error.Values:
"error", "warning", "info"Default: "error"string
required
ISO 8601 timestamp when the error occurred.Format:
YYYY-MM-DDTHH:mm:ss.sssZExample: "2024-01-01T12:34:56.789Z"string
required
Identifier for the application that generated the error.Example:
"my-frontend-app", "backend-api", "mobile-app"string
required
Environment where the error occurred.Example:
"production", "staging", "development"object
Custom metadata attached to the error.Example:
{
"userId": "user-123",
"userEmail": "user@example.com",
"feature": "checkout",
"orderId": "order-456",
"version": "2.1.0"
}
object
Environmental context (browser or Node.js information).Browser Context:Node.js Context:
{
"viewport": { "width": 1920, "height": 1080 },
"screen": { "width": 1920, "height": 1080, "colorDepth": 24 },
"browser": {
"userAgent": "Mozilla/5.0...",
"language": "en-US",
"platform": "MacIntel"
},
"network": {
"effectiveType": "4g",
"downlink": 10,
"rtt": 50
}
}
{
"process": {
"pid": 12345,
"platform": "linux",
"arch": "x64",
"nodeVersion": "v20.0.0",
"memory": {
"rss": 50000000,
"heapUsed": 20000000
}
}
}
Response
boolean
Indicates whether the log was successfully stored.
string
MongoDB ObjectId of the created log entry.
string
Fingerprint hash used for error grouping.
Example Request
curl -X POST https://vij.example.com/api/logs \
-H "Content-Type: application/json" \
-d '{
"message": "Payment processing failed",
"name": "Error",
"stack": "Error: Payment processing failed\n at processPayment (payment.js:45:12)",
"severity": "error",
"timestamp": "2024-01-01T12:34:56.789Z",
"appId": "my-app",
"environment": "production",
"metadata": {
"userId": "user-123",
"orderId": "order-456"
},
"context": {
"viewport": { "width": 1920, "height": 1080 }
}
}'
const response = await fetch('https://vij.example.com/api/logs', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: 'Payment processing failed',
name: 'Error',
stack: 'Error: Payment processing failed\n at processPayment (payment.js:45:12)',
severity: 'error',
timestamp: new Date().toISOString(),
appId: 'my-app',
environment: 'production',
metadata: {
userId: 'user-123',
orderId: 'order-456'
},
context: {
viewport: { width: window.innerWidth, height: window.innerHeight }
}
})
});
const data = await response.json();
console.log(data);
import requests
import json
from datetime import datetime
response = requests.post(
'https://vij.example.com/api/logs',
headers={'Content-Type': 'application/json'},
json={
'message': 'Payment processing failed',
'name': 'Error',
'stack': 'Error: Payment processing failed\n at processPayment (payment.js:45:12)',
'severity': 'error',
'timestamp': datetime.utcnow().isoformat() + 'Z',
'appId': 'my-app',
'environment': 'production',
'metadata': {
'userId': 'user-123',
'orderId': 'order-456'
},
'context': {}
}
)
print(response.json())
Example Response
{
"success": true,
"id": "65a1b2c3d4e5f6g7h8i9j0k1",
"group": "abc123def456"
}
Error Responses
{
"error": "Missing required field: message"
}
{
"error": "Invalid severity level. Must be 'error', 'warning', or 'info'"
}
{
"error": "Failed to store log entry"
}
Validation Rules
Required Fields
Required Fields
All of these fields must be present:
messagenamestackseveritytimestampappIdenvironment
metadata(defaults to{})context(defaults to{})
Severity Values
Severity Values
severity must be one of:"error"- Critical errors"warning"- Non-critical issues"info"- Informational logs
Timestamp Format
Timestamp Format
timestamp must be a valid ISO 8601 string:- Format:
YYYY-MM-DDTHH:mm:ss.sssZ - Example:
2024-01-01T12:34:56.789Z - Must be in UTC (Z timezone)
String Length Limits
String Length Limits
message: Max 10,000 charactersname: Max 500 charactersstack: Max 50,000 charactersappId: Max 100 charactersenvironment: Max 50 characters
Rate Limiting
VIJ Admin does not enforce rate limiting by default. However, you should implement rate limiting in production to prevent abuse.
- 1,000 requests per minute per IP
- 10,000 requests per hour per appId
// Add to middleware
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 1000,
message: 'Too many requests'
});
app.use('/api/logs', limiter);
CORS Configuration
VIJ Admin allows all origins by default for the/api/logs endpoint.
Default CORS headers:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
// In next.config.js
module.exports = {
async headers() {
return [
{
source: '/api/logs',
headers: [
{ key: 'Access-Control-Allow-Origin', value: 'https://your-app.com' },
{ key: 'Access-Control-Allow-Methods', value: 'POST, OPTIONS' },
{ key: 'Access-Control-Allow-Headers', value: 'Content-Type' }
]
}
];
}
};
Processing Pipeline
When a log is received, VIJ processes it through these steps:- Validation - Check required fields and formats
- Fingerprinting - Generate error group fingerprint
- Storage - Insert into MongoDB
logscollection - Indexing - Update MongoDB indexes
- Grouping - Update error group counters
- Response - Return success with log ID
Batch Logging
Send multiple logs in a single request:POST /api/logs/batch
{
"logs": [
{ /* log 1 */ },
{ /* log 2 */ },
{ /* log 3 */ }
]
}
{
"success": true,
"inserted": 3,
"ids": ["id1", "id2", "id3"]
}
The vij-sdk automatically batches logs to reduce network overhead.
Security Considerations
No authentication by default
No authentication by default
VIJ Admin does not require authentication for the
/api/logs endpoint by default.Add authentication:// middleware.ts
export function middleware(request: NextRequest) {
const apiKey = request.headers.get('x-api-key');
if (apiKey !== process.env.API_KEY) {
return new NextResponse('Unauthorized', { status: 401 });
}
return NextResponse.next();
}
export const config = {
matcher: '/api/logs'
};
Sanitize sensitive data
Sanitize sensitive data
Never log sensitive information:
- Passwords
- API keys
- Credit card numbers
- Social security numbers
- Personal identification
const sanitizedMetadata = {
...metadata,
password: undefined,
apiKey: undefined,
creditCard: metadata.creditCard ? 'REDACTED' : undefined
};
Validate input size
Validate input size
Prevent large payloads from overwhelming the server:
// In API route
const MAX_PAYLOAD_SIZE = 1024 * 100; // 100 KB
if (JSON.stringify(req.body).length > MAX_PAYLOAD_SIZE) {
return res.status(413).json({ error: 'Payload too large' });
}
Troubleshooting
400 Bad Request
400 Bad Request
Common causes:
- Missing required fields
- Invalid severity value
- Invalid timestamp format
- Malformed JSON
413 Payload Too Large
413 Payload Too Large
Cause: Request body exceeds size limitSolution:
- Reduce stack trace length
- Minimize metadata
- Use batch endpoint for multiple logs
500 Internal Server Error
500 Internal Server Error
Common causes:
- MongoDB connection failure
- Database write error
- Server configuration issue
CORS errors
CORS errors
Symptom: Browser blocks requestSolution: Ensure CORS headers allow your domain or use
* for all originsRelated Endpoints
GET /api/logs
Retrieve stored error logs
GET /api/stats
Get error statistics and metrics
GET /api/groups
Retrieve error groups
SDK Reference
Use vij-sdk for automatic logging