Initial release v0.0.1: go-sink - A tool for sending logs to error tracking systems

This commit is contained in:
Leopere 2025-04-14 15:59:05 -04:00
parent 4526d05aef
commit 32f6560382
26 changed files with 662 additions and 54 deletions

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
# Build Logs
build_logs/
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# IDE and editor files
.cursor/
.vscode/
.idea/
*.swp
*.swo
# Log files
*.log
log.json

142
README.md Normal file
View File

@ -0,0 +1,142 @@
You can stream and read files to a sentry DSN glitchtip or sentry itself I was fed up with how brittle sentry-cli was so I made this.
I might get around to making a whole CI/CD process for this which will upload the executables properly at some point and make this public.
# Go Glitch
Go Glitch is a command-line utility that reads log messages from a file or stdin and sends them to Glitchtip, Sentry, or any compatible error tracking system.
## Installation
To install the binary locally, you can use the provided installation script. Run the following command in your terminal:
```sh
curl -sSL https://git.nixc.us/Nixius/go-glitch/raw/branch/master/install.sh | bash
```
This will download and install the Go Glitch binary to your local machine.
## Usage
You can use Go Glitch by specifying a log file as an argument or by piping input to it.
### Using a Log File
```sh
go-glitch /path/to/logfile
```
### Using Piped Input
```sh
cat /path/to/logfile | go-glitch
```
### Server Mode
You can also run Go Glitch in server mode, which starts a webhook server that accepts log messages via HTTP requests:
```sh
go-glitch server
```
The server listens on port 5050 by default. You can send log messages to the webhook endpoint:
```sh
curl -X POST -H "Content-Type: application/json" \
-d '{"log_level": "error", "message": "Error message", "service": "my-service"}' \
http://localhost:5050/webhook
```
## Configuration
Go Glitch supports multiple environment variables for the DSN. You can use any of the following:
- `GLITCHTIP_DSN`: For GlitchTip instances
- `SENTRY_DSN`: For Sentry instances
- `BUGSINK_DSN`: For Bugsink instances
If multiple environment variables are set, they are checked in the order listed above, and the first one found is used.
### Setting the DSN
#### Shell Environment
Add one of the following lines to your `.zshrc` or `.bashrc` file:
```sh
export GLITCHTIP_DSN="your-dsn"
# OR
export SENTRY_DSN="your-dsn"
# OR
export BUGSINK_DSN="your-dsn"
```
After adding the line, reload your shell configuration:
```sh
source ~/.zshrc # for zsh users
source ~/.bashrc # for bash users
```
#### Dockerfile
If you are using a Docker container, add the environment variable in your `Dockerfile`:
```Dockerfile
ENV GLITCHTIP_DSN=your-dsn
# OR
ENV SENTRY_DSN=your-dsn
# OR
ENV BUGSINK_DSN=your-dsn
```
#### docker-compose.yml
If you are using Docker Compose, add the environment variable in your `docker-compose.yml` file:
```yaml
version: '3.8'
services:
go-glitch:
image: your-docker-image
environment:
- GLITCHTIP_DSN=your-dsn
# OR
# - SENTRY_DSN=your-dsn
# OR
# - BUGSINK_DSN=your-dsn
```
## Development
### Testing Protocol
Go Glitch includes a comprehensive test suite in `build-run-test.sh` that ensures all functionality works correctly:
1. **Build and Verification**: The script builds the application for all supported platforms and verifies that the binaries are created.
2. **Environment Variable Tests**: Each supported DSN environment variable is tested with both plain text and JSON input:
- `GLITCHTIP_DSN`
- `SENTRY_DSN`
- `BUGSINK_DSN`
3. **Priority Tests**: The script tests that when multiple environment variables are set, the correct order of precedence is followed.
4. **Webhook Tests**: The server mode is tested with both single and batch webhook requests.
To run the tests, simply execute:
```sh
./build-run-test.sh
```
The script provides clear, color-coded output with pass/fail indicators for each test.
### Contributing
Before submitting changes, please ensure that:
1. You have run `./build-run-test.sh` to verify that all tests pass
2. Your changes maintain compatibility with all supported DSN environment variables
3. Any new features include appropriate tests in the test suite

119
build-run-test.sh Executable file
View File

@ -0,0 +1,119 @@
#!/bin/bash
# Exit on any error
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Set the DSN
DSN="https://725ff4678a4b447c8aa22450b64d66ae@bugsink.aenow.com/6"
echo -e "${BLUE}Testing with multiple environment variable options for DSN${NC}"
echo "DSN: $DSN"
# Function to verify results
verify_result() {
if [ $? -eq 0 ]; then
echo -e "${GREEN}✅ Test passed${NC}"
else
echo -e "${RED}❌ Test failed${NC}"
exit 1
fi
}
# Build the application using build.sh
echo -e "\n${BLUE}Building go-glitch using build.sh...${NC}"
./build.sh
verify_result
# Check if the darwin_arm64 binary exists
if [ ! -f "./dist/go-glitch_darwin_arm64" ]; then
echo -e "${RED}❌ Binary for darwin/arm64 not found. Make sure build.sh completed successfully.${NC}"
exit 1
else
echo -e "${GREEN}✅ Binary found${NC}"
fi
# Check for running server instances and kill them
echo -e "\n${BLUE}Checking for existing go-glitch server instances...${NC}"
pkill -f "go-glitch.*server" || true
sleep 1
# Test 1a: Using GLITCHTIP_DSN with plain text
echo -e "\n${YELLOW}Test 1a: Using GLITCHTIP_DSN environment variable with plain text...${NC}"
export GLITCHTIP_DSN="$DSN"
export SENTRY_DSN=""
export BUGSINK_DSN=""
echo "Plain text test with GLITCHTIP_DSN" | ./dist/go-glitch_darwin_arm64
verify_result
# Test 1b: Using GLITCHTIP_DSN with JSON
echo -e "\n${YELLOW}Test 1b: Using GLITCHTIP_DSN environment variable with JSON...${NC}"
echo '{"log_level": "error", "message": "JSON test with GLITCHTIP_DSN", "service": "test-suite", "test_id": "1b"}' | ./dist/go-glitch_darwin_arm64
verify_result
# Test 2a: Using SENTRY_DSN with plain text
echo -e "\n${YELLOW}Test 2a: Using SENTRY_DSN environment variable with plain text...${NC}"
export GLITCHTIP_DSN=""
export SENTRY_DSN="$DSN"
export BUGSINK_DSN=""
echo "Plain text test with SENTRY_DSN" | ./dist/go-glitch_darwin_arm64
verify_result
# Test 2b: Using SENTRY_DSN with JSON
echo -e "\n${YELLOW}Test 2b: Using SENTRY_DSN environment variable with JSON...${NC}"
echo '{"log_level": "error", "message": "JSON test with SENTRY_DSN", "service": "test-suite", "test_id": "2b"}' | ./dist/go-glitch_darwin_arm64
verify_result
# Test 3a: Using BUGSINK_DSN with plain text
echo -e "\n${YELLOW}Test 3a: Using BUGSINK_DSN environment variable with plain text...${NC}"
export GLITCHTIP_DSN=""
export SENTRY_DSN=""
export BUGSINK_DSN="$DSN"
echo "Plain text test with BUGSINK_DSN" | ./dist/go-glitch_darwin_arm64
verify_result
# Test 3b: Using BUGSINK_DSN with JSON
echo -e "\n${YELLOW}Test 3b: Using BUGSINK_DSN environment variable with JSON...${NC}"
echo '{"log_level": "error", "message": "JSON test with BUGSINK_DSN", "service": "test-suite", "test_id": "3b"}' | ./dist/go-glitch_darwin_arm64
verify_result
# Test environment variable priority (GLITCHTIP_DSN should take precedence)
echo -e "\n${YELLOW}Test 4: Testing environment variable priority...${NC}"
export GLITCHTIP_DSN="$DSN"
export SENTRY_DSN="$DSN"
export BUGSINK_DSN="$DSN"
echo "Testing environment variable priority" | ./dist/go-glitch_darwin_arm64
verify_result
# Start the server with GLITCHTIP_DSN for webhook tests
echo -e "\n${BLUE}Starting go-glitch server with GLITCHTIP_DSN...${NC}"
export GLITCHTIP_DSN="$DSN"
export SENTRY_DSN=""
export BUGSINK_DSN=""
./dist/go-glitch_darwin_arm64 server &
SERVER_PID=$!
# Give the server time to start
echo "Waiting for server to start..."
sleep 2
# Test 5: Send webhook request
echo -e "\n${YELLOW}Test 5: Sending a webhook request...${NC}"
curl -X POST -H "Content-Type: application/json" -d '{"log_level": "error", "message": "Error via webhook", "service": "test-suite", "test_id": "5"}' http://localhost:5050/webhook
verify_result
# Test 6: Send webhook batch request
echo -e "\n${YELLOW}Test 6: Sending a webhook batch request...${NC}"
curl -X POST -H "Content-Type: application/json" -d '[{"log_level": "error", "message": "Error in batch", "service": "test-suite", "test_id": "6.1"},{"log_level": "warning", "message": "Warning in batch", "service": "test-suite", "test_id": "6.2"}]' http://localhost:5050/webhook
verify_result
# Clean up the server
echo -e "\n${BLUE}Tests completed. Shutting down server...${NC}"
kill $SERVER_PID || true
echo -e "\n${GREEN}All tests passed successfully!${NC}"

43
build.sh Executable file
View File

@ -0,0 +1,43 @@
#!/bin/bash
ARCHITECTURES=("linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64" "windows/amd64")
PROJECT_NAME=$(basename "$(pwd)")
prepare_build() {
mkdir -p dist build_logs
if [ ! -f go.mod ]; then
go mod init "$PROJECT_NAME"
fi
go mod tidy
}
build_binary() {
local os=$1
local arch=$2
local output="dist/${PROJECT_NAME}_${os}_${arch}"
env GOOS=$os GOARCH=$arch go build -o $output . &> "build_logs/${os}_${arch}.log"
if [ $? -ne 0 ]; then
echo "Build failed for $os/$arch" >> "build_logs/error.log"
else
echo "Build succeeded for $os/$arch"
fi
if [ "$os" == "linux" ]; then
env GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o "${output}_static" . &> "build_logs/${os}_${arch}_static.log"
if [ $? -ne 0 ]; then
echo "Static build failed for $os/$arch" >> "build_logs/error.log"
else
echo "Static build succeeded for $os/$arch"
fi
fi
}
main() {
prepare_build
for arch in "${ARCHITECTURES[@]}"; do
IFS="/" read -r os arch <<< "$arch"
build_binary $os $arch
done
echo "Build process completed."
}
main

View File

View File

7
build_logs/error.log Normal file
View File

@ -0,0 +1,7 @@
Build failed for linux/amd64
Static build failed for linux/amd64
Build failed for linux/arm64
Static build failed for linux/arm64
Build failed for darwin/amd64
Build failed for darwin/arm64
Build failed for windows/amd64

View File

View File

View File

View File

View File

BIN
dist/go-glitch_darwin_amd64 vendored Executable file

Binary file not shown.

Binary file not shown.

BIN
dist/go-glitch_linux_amd64 vendored Executable file

Binary file not shown.

BIN
dist/go-glitch_linux_amd64_static vendored Executable file

Binary file not shown.

BIN
dist/go-glitch_linux_arm64 vendored Executable file

Binary file not shown.

BIN
dist/go-glitch_linux_arm64_static vendored Executable file

Binary file not shown.

BIN
dist/go-glitch_windows_amd64 vendored Executable file

Binary file not shown.

View File

@ -1,53 +0,0 @@
package main
import (
"fmt"
"os"
"time"
"github.com/getsentry/sentry-go"
)
func main() {
// Check for the right number of command line arguments
if len(os.Args) != 2 {
fmt.Println("Usage: ./go-glitch \"Your log message here\"")
os.Exit(1)
}
logMessage := os.Args[1]
dsn := os.Getenv("SENTRY_DSN")
if dsn == "" {
fmt.Println("Error: SENTRY_DSN environment variable is not set.")
os.Exit(1)
}
// Initialize Sentry with the DSN
err := sentry.Init(sentry.ClientOptions{
Dsn: dsn,
})
if err != nil {
fmt.Printf("Error initializing Sentry: %s\n", err)
os.Exit(1)
}
// Defer a function to flush Sentry's buffer
defer func() {
success := sentry.Flush(5 * time.Second)
if !success {
fmt.Println("Failed to flush Sentry buffer within the expected time.")
} else {
fmt.Println("Sentry buffer flushed successfully.")
}
}()
// Capture the message and output the event ID
eventID := sentry.CaptureMessage(logMessage)
if eventID != nil {
fmt.Printf("Sent message to Sentry with event ID: %s\n", *eventID)
} else {
fmt.Println("Failed to send message to Sentry.")
}
}

3
go.mod
View File

@ -2,8 +2,9 @@ module go-glitch
go 1.21.1
require github.com/getsentry/sentry-go v0.27.0
require (
github.com/getsentry/sentry-go v0.27.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
)

16
go.sum
View File

@ -1,6 +1,22 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

34
install.sh Normal file
View File

@ -0,0 +1,34 @@
#!/bin/bash
INSTALL_DIR="/usr/local/bin"
BINARY_NAME="go-glitch"
BASE_URL="https://git.nixc.us/Nixius/go-glitch/raw/branch/master/dist"
# Supported architectures
ARCHITECTURES=("linux/amd64" "linux/arm64" "linux/arm/v7" "darwin/amd64" "darwin/arm64")
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"
case $ARCH in
x86_64) ARCH="amd64" ;;
arm64 | aarch64) ARCH="arm64" ;;
arm*) ARCH="arm/v7" ;;
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
esac
BINARY_URL="${BASE_URL}/${BINARY_NAME}_${OS}_${ARCH}"
echo "Downloading and installing $BINARY_NAME from $BINARY_URL..."
# Check if we have write permission to the install directory
if [ -w "${INSTALL_DIR}" ]; then
curl -sSL "$BINARY_URL" -o "${INSTALL_DIR}/${BINARY_NAME}"
chmod +x "${INSTALL_DIR}/${BINARY_NAME}"
else
curl -sSL "$BINARY_URL" -o "${INSTALL_DIR}/${BINARY_NAME}"
chmod +x "${INSTALL_DIR}/${BINARY_NAME}"
fi
echo "Installed $BINARY_NAME to $INSTALL_DIR"

1
log.log Normal file
View File

@ -0,0 +1 @@
test

272
main.go Normal file
View File

@ -0,0 +1,272 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"time"
"github.com/getsentry/sentry-go"
)
func main() {
// Try multiple environment variables for DSN
dsn := getEnvDSN()
if dsn == "" {
fmt.Fprintf(os.Stderr, "Error: No DSN environment variable found.\n")
fmt.Fprintf(os.Stderr, "Please set one of the following environment variables:\n")
fmt.Fprintf(os.Stderr, " - GLITCHTIP_DSN\n")
fmt.Fprintf(os.Stderr, " - SENTRY_DSN\n")
fmt.Fprintf(os.Stderr, " - BUGSINK_DSN\n")
os.Exit(1)
}
// Initialize with the DSN found
err := sentry.Init(sentry.ClientOptions{
Dsn: dsn,
})
if err != nil {
fmt.Fprintf(os.Stderr, "Error initializing client: %s\n", err)
os.Exit(1)
}
if len(os.Args) > 1 && os.Args[1] == "server" {
startServer()
} else {
processLogs()
}
defer func() {
success := sentry.Flush(10 * time.Second)
if !success {
fmt.Fprintf(os.Stderr, "Failed to flush buffer within the expected time.\n")
} else {
fmt.Println("GlitchTip buffer flushed successfully.")
}
}()
}
// getEnvDSN checks multiple environment variables for a DSN
func getEnvDSN() string {
// Try different environment variables in order of preference
envVars := []string{
"GLITCHTIP_DSN",
"SENTRY_DSN",
"BUGSINK_DSN",
}
for _, envVar := range envVars {
if dsn := os.Getenv(envVar); dsn != "" {
fmt.Printf("Using DSN from %s environment variable\n", envVar)
return dsn
}
}
return ""
}
func processLogs() {
var reader io.Reader
var filePath string
if len(os.Args) == 2 {
filePath = os.Args[1]
file, err := os.Open(filePath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error opening file: %s\n", err)
os.Exit(1)
}
defer file.Close()
reader = file
fmt.Println("Reading from file:", filePath)
} else if isInputFromPipe() {
reader = os.Stdin
fmt.Println("Reading from stdin")
} else {
fmt.Fprintf(os.Stderr, "Usage: %s <path to log file> OR pipe input\n", os.Args[0])
os.Exit(1)
}
scanner := bufio.NewScanner(reader)
var content strings.Builder
for scanner.Scan() {
line := scanner.Text()
content.WriteString(line)
content.WriteString("\n")
fmt.Println("Read line:", line)
}
if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Error reading input: %s\n", err)
os.Exit(1)
}
logMessage := content.String()
fmt.Println("Final log message:", logMessage)
sendToSentry(logMessage)
}
func isInputFromPipe() bool {
fileInfo, err := os.Stdin.Stat()
if err != nil {
return false
}
return fileInfo.Mode()&os.ModeCharDevice == 0
}
func sendToSentry(logMessage string) {
fmt.Println("Sending log message to Sentry:", logMessage)
// Try to parse as JSON first
var parsedLog map[string]interface{}
err := json.Unmarshal([]byte(logMessage), &parsedLog)
if err == nil {
// Successfully parsed JSON
// Check for common log fields
level := "info"
message := logMessage
tags := make(map[string]string)
if lvl, ok := parsedLog["level"].(string); ok {
level = lvl
} else if lvl, ok := parsedLog["log_level"].(string); ok {
level = lvl
}
if msg, ok := parsedLog["message"].(string); ok {
message = msg
} else if msg, ok := parsedLog["msg"].(string); ok {
message = msg
}
// Add any additional fields as tags
for k, v := range parsedLog {
if k != "level" && k != "log_level" && k != "message" && k != "msg" {
if strVal, ok := v.(string); ok {
tags[k] = strVal
} else if jsonVal, err := json.Marshal(v); err == nil {
tags[k] = string(jsonVal)
}
}
}
// Create a more structured event based on the level
var eventID *sentry.EventID
sentry.WithScope(func(scope *sentry.Scope) {
scope.SetLevel(getSentryLevel(level))
// Add all the extracted fields as tags
for k, v := range tags {
scope.SetTag(k, v)
}
// Capture based on level
if getSentryLevel(level) >= sentry.LevelError {
eventID = sentry.CaptureException(fmt.Errorf(message))
} else {
eventID = sentry.CaptureMessage(message)
}
})
if eventID != nil {
fmt.Printf("Sent message to GlitchTip with event ID: %s\n", *eventID)
} else {
fmt.Fprintf(os.Stderr, "Failed to send message to GlitchTip.\n")
}
} else {
// Not a valid JSON, send as a simple message
eventID := sentry.CaptureMessage(logMessage)
if eventID != nil {
fmt.Printf("Sent message to GlitchTip with event ID: %s\n", *eventID)
} else {
fmt.Fprintf(os.Stderr, "Failed to send message to GlitchTip.\n")
}
}
}
// Helper function to convert string log levels to Sentry levels
func getSentryLevel(level string) sentry.Level {
switch strings.ToLower(level) {
case "fatal", "panic":
return sentry.LevelFatal
case "error", "err":
return sentry.LevelError
case "warning", "warn":
return sentry.LevelWarning
case "info":
return sentry.LevelInfo
case "debug":
return sentry.LevelDebug
default:
return sentry.LevelInfo
}
}
func startServer() {
http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
bodyBytes, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Error reading request body", http.StatusInternalServerError)
fmt.Fprintf(os.Stderr, "Error reading request body: %s\n", err)
return
}
fmt.Printf("Received request body: %s\n", bodyBytes) // Log the full request body
var logMessage interface{}
err = json.Unmarshal(bodyBytes, &logMessage)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
fmt.Fprintf(os.Stderr, "Invalid JSON: %s\nReceived: %s\n", err, string(bodyBytes))
return
}
switch v := logMessage.(type) {
case map[string]interface{}:
logBytes, err := json.Marshal(v)
if err != nil {
http.Error(w, "Error processing log message", http.StatusInternalServerError)
fmt.Fprintf(os.Stderr, "Error processing log message: %s\n", err)
return
}
fmt.Printf("Processed log: %s\n", logBytes)
sendToSentry(string(logBytes))
case []interface{}:
for _, item := range v {
if logItem, ok := item.(map[string]interface{}); ok {
logBytes, err := json.Marshal(logItem)
if err != nil {
http.Error(w, "Error processing log message", http.StatusInternalServerError)
fmt.Fprintf(os.Stderr, "Error processing log message: %s\n", err)
return
}
fmt.Printf("Processed log item: %s\n", logBytes)
sendToSentry(string(logBytes))
} else {
http.Error(w, "Invalid JSON item in array", http.StatusBadRequest)
fmt.Fprintf(os.Stderr, "Invalid JSON item in array: %s\n", item)
return
}
}
default:
http.Error(w, "Invalid JSON format", http.StatusBadRequest)
fmt.Fprintf(os.Stderr, "Invalid JSON format: %s\n", v)
return
}
w.WriteHeader(http.StatusNoContent)
})
fmt.Println("Starting server on 0.0.0.0:5050")
err := http.ListenAndServe("0.0.0.0:5050", nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Error starting server: %s\n", err)
os.Exit(1)
}
}

4
test.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
curl -X POST -H "Content-Type: application/json" -d '{"log_level": "error", "message": "This is an error log message via TEST.SH"}' http://localhost:5050/webhook
echo test | ./dist/go-glitch_darwin_arm64
./dist/go-glitch_darwin_arm64 log.log