diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e2ca74 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8ba676b --- /dev/null +++ b/README.md @@ -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 diff --git a/build-run-test.sh b/build-run-test.sh new file mode 100755 index 0000000..514d7e3 --- /dev/null +++ b/build-run-test.sh @@ -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}" \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..03907e4 --- /dev/null +++ b/build.sh @@ -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 diff --git a/build_logs/darwin_amd64.log b/build_logs/darwin_amd64.log new file mode 100644 index 0000000..e69de29 diff --git a/build_logs/darwin_arm64.log b/build_logs/darwin_arm64.log new file mode 100644 index 0000000..e69de29 diff --git a/build_logs/error.log b/build_logs/error.log new file mode 100644 index 0000000..4f7d59e --- /dev/null +++ b/build_logs/error.log @@ -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 diff --git a/build_logs/linux_amd64.log b/build_logs/linux_amd64.log new file mode 100644 index 0000000..e69de29 diff --git a/build_logs/linux_amd64_static.log b/build_logs/linux_amd64_static.log new file mode 100644 index 0000000..e69de29 diff --git a/build_logs/linux_arm64.log b/build_logs/linux_arm64.log new file mode 100644 index 0000000..e69de29 diff --git a/build_logs/linux_arm64_static.log b/build_logs/linux_arm64_static.log new file mode 100644 index 0000000..e69de29 diff --git a/build_logs/windows_amd64.log b/build_logs/windows_amd64.log new file mode 100644 index 0000000..e69de29 diff --git a/dist/go-glitch_darwin_amd64 b/dist/go-glitch_darwin_amd64 new file mode 100755 index 0000000..8d5df3b Binary files /dev/null and b/dist/go-glitch_darwin_amd64 differ diff --git a/go-glitch b/dist/go-glitch_darwin_arm64 similarity index 55% rename from go-glitch rename to dist/go-glitch_darwin_arm64 index 421132f..5e3b19b 100755 Binary files a/go-glitch and b/dist/go-glitch_darwin_arm64 differ diff --git a/dist/go-glitch_linux_amd64 b/dist/go-glitch_linux_amd64 new file mode 100755 index 0000000..5816d36 Binary files /dev/null and b/dist/go-glitch_linux_amd64 differ diff --git a/dist/go-glitch_linux_amd64_static b/dist/go-glitch_linux_amd64_static new file mode 100755 index 0000000..eac8838 Binary files /dev/null and b/dist/go-glitch_linux_amd64_static differ diff --git a/dist/go-glitch_linux_arm64 b/dist/go-glitch_linux_arm64 new file mode 100755 index 0000000..4c28ba8 Binary files /dev/null and b/dist/go-glitch_linux_arm64 differ diff --git a/dist/go-glitch_linux_arm64_static b/dist/go-glitch_linux_arm64_static new file mode 100755 index 0000000..7cff750 Binary files /dev/null and b/dist/go-glitch_linux_arm64_static differ diff --git a/dist/go-glitch_windows_amd64 b/dist/go-glitch_windows_amd64 new file mode 100755 index 0000000..2f4d787 Binary files /dev/null and b/dist/go-glitch_windows_amd64 differ diff --git a/go-glitch.go b/go-glitch.go deleted file mode 100644 index 1d0a9ad..0000000 --- a/go-glitch.go +++ /dev/null @@ -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.") - } -} - diff --git a/go.mod b/go.mod index 7036b78..2af2912 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 121a9cb..b3daf4a 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..706f21a --- /dev/null +++ b/install.sh @@ -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" diff --git a/log.log b/log.log new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/log.log @@ -0,0 +1 @@ +test diff --git a/main.go b/main.go new file mode 100644 index 0000000..7a30d10 --- /dev/null +++ b/main.go @@ -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 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) + } +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..90cf282 --- /dev/null +++ b/test.sh @@ -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 \ No newline at end of file