From 4d207ad3f993a78856710ff04c92c0b8c534f5e5 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sat, 15 Nov 2025 20:27:23 +0700 Subject: [PATCH] feat: init --- index.js | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 5 ++ wrangler.toml | 3 ++ 3 files changed, 151 insertions(+) create mode 100644 index.js create mode 100644 package.json create mode 100644 wrangler.toml diff --git a/index.js b/index.js new file mode 100644 index 0000000..cb43efe --- /dev/null +++ b/index.js @@ -0,0 +1,143 @@ +// Cloudflare Worker to forward logs to Grafana Cloud Loki + +async function handleRequest(request, env) { + const url = new URL(request.url); + + // Redirect to GitHub repo for documentation + if (request.method === 'GET') { + return Response.redirect('https://github.com/tiennnm99/miti-loki', 302); + } + + // Only handle POST requests + if (request.method !== 'POST') { + return new Response('Method not allowed. Please use POST.', { + status: 405, + headers: { + 'Access-Control-Allow-Origin': '*', + } + }); + } + + // Validate environment variables + if (!env.LOKI_HOST || !env.LOKI_USERNAME || !env.LOKI_PASSWORD) { + return new Response('Server configuration error: Missing Loki credentials', { + status: 500, + headers: { + 'Access-Control-Allow-Origin': '*', + } + }); + } + + try { + // Read the request body + const body = await request.text(); + + // Validate that body is not empty + if (!body) { + return new Response('Request body is required', { + status: 400, + headers: { + 'Access-Control-Allow-Origin': '*', + } + }); + } + + // Validate JSON format + try { + const jsonBody = JSON.parse(body); + if (!jsonBody.streams || !Array.isArray(jsonBody.streams)) { + return new Response('Invalid format: "streams" array is required', { + status: 400, + headers: { + 'Access-Control-Allow-Origin': '*', + } + }); + } + } catch (parseError) { + return new Response(`Invalid JSON: ${parseError.message}`, { + status: 400, + headers: { + 'Access-Control-Allow-Origin': '*', + } + }); + } + + // Construct Loki URL + const lokiPort = env.LOKI_PORT || '443'; + const protocol = lokiPort === '443' ? 'https' : 'http'; + const lokiUrl = `${protocol}://${env.LOKI_HOST}:${lokiPort}/loki/api/v1/push`; + + // Create Basic Auth header + const auth = btoa(`${env.LOKI_USERNAME}:${env.LOKI_PASSWORD}`); + + // Forward request to Loki + const lokiResponse = await fetch(lokiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Basic ${auth}`, + }, + body: body, + }); + + // Get response text + const responseText = await lokiResponse.text(); + + // Return the response from Loki with CORS headers + return new Response(responseText, { + status: lokiResponse.status, + statusText: lokiResponse.statusText, + headers: { + 'Content-Type': lokiResponse.headers.get('Content-Type') || 'text/plain', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + }, + }); + + } catch (error) { + return new Response(`Error forwarding to Loki: ${error.message}`, { + status: 500, + headers: { + 'Access-Control-Allow-Origin': '*', + } + }); + } +} + +// Handle OPTIONS requests for CORS +function handleOptions(request) { + const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + 'Access-Control-Max-Age': '86400', + }; + + return new Response(null, { + status: 204, + headers: corsHeaders, + }); +} + +// Main event listener for the worker +addEventListener('fetch', event => { + const request = event.request; + + // Handle CORS preflight requests + if (request.method === 'OPTIONS') { + event.respondWith(handleOptions(request)); + } else { + event.respondWith(handleRequest(request, event.env || {})); + } +}); + +// Export for ES modules (newer Workers format) +export default { + async fetch(request, env) { + if (request.method === 'OPTIONS') { + return handleOptions(request); + } + return handleRequest(request, env); + } +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..63e8a42 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "wrangler": "^4.0.0" + } +} diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..396fe37 --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,3 @@ +name = "miti-loki" +main = "index.js" +compatibility_date = "2024-12-31"