Parseable

GitHub Actions

Send CI/CD pipeline logs from GitHub Actions to Parseable


Collect and analyze GitHub Actions workflow logs in Parseable for CI/CD observability.

Overview

Integrate GitHub Actions with Parseable to:

  • Centralize CI/CD Logs - Collect all workflow logs in one place
  • Debug Failures - Quickly find and analyze failed jobs
  • Track Performance - Monitor build times and trends
  • Audit Pipelines - Maintain compliance with log retention

Prerequisites

  • GitHub repository with Actions enabled
  • Parseable instance accessible from GitHub Actions runners
  • GitHub Personal Access Token (for API access)

Method 1: Direct Log Shipping

Send logs directly from your workflow to Parseable.

Workflow Configuration

Add a step to send logs to Parseable:

name: CI Pipeline

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Build
        id: build
        run: |
          echo "Starting build..."
          # Your build commands here
          npm ci
          npm run build
          echo "Build completed successfully"
      
      - name: Send logs to Parseable
        if: always()
        run: |
          curl -X POST "${{ secrets.PARSEABLE_URL }}/api/v1/ingest" \
            -H "Authorization: Basic ${{ secrets.PARSEABLE_AUTH }}" \
            -H "X-P-Stream: github-actions" \
            -H "Content-Type: application/json" \
            -d '[{
              "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
              "repository": "${{ github.repository }}",
              "workflow": "${{ github.workflow }}",
              "job": "${{ github.job }}",
              "run_id": "${{ github.run_id }}",
              "run_number": "${{ github.run_number }}",
              "actor": "${{ github.actor }}",
              "event": "${{ github.event_name }}",
              "ref": "${{ github.ref }}",
              "sha": "${{ github.sha }}",
              "status": "${{ job.status }}",
              "conclusion": "${{ steps.build.conclusion }}"
            }]'

Reusable Action

Create a reusable action for consistent logging:

# .github/actions/log-to-parseable/action.yml
name: Log to Parseable
description: Send workflow logs to Parseable

inputs:
  parseable_url:
    description: Parseable instance URL
    required: true
  parseable_auth:
    description: Base64 encoded credentials
    required: true
  dataset:
    description: Parseable dataset name
    default: github-actions
  status:
    description: Job status
    required: true
  message:
    description: Log message
    default: ''

runs:
  using: composite
  steps:
    - name: Send to Parseable
      shell: bash
      run: |
        curl -X POST "${{ inputs.parseable_url }}/api/v1/ingest" \
          -H "Authorization: Basic ${{ inputs.parseable_auth }}" \
          -H "X-P-Stream: ${{ inputs.dataset }}" \
          -H "Content-Type: application/json" \
          -d '[{
            "timestamp": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'",
            "repository": "${{ github.repository }}",
            "workflow": "${{ github.workflow }}",
            "job": "${{ github.job }}",
            "run_id": "${{ github.run_id }}",
            "run_number": "${{ github.run_number }}",
            "actor": "${{ github.actor }}",
            "event": "${{ github.event_name }}",
            "ref": "${{ github.ref }}",
            "sha": "${{ github.sha }}",
            "status": "${{ inputs.status }}",
            "message": "${{ inputs.message }}"
          }]'

Use the action in your workflow:

- name: Log success
  if: success()
  uses: ./.github/actions/log-to-parseable
  with:
    parseable_url: ${{ secrets.PARSEABLE_URL }}
    parseable_auth: ${{ secrets.PARSEABLE_AUTH }}
    status: success
    message: Build completed successfully

- name: Log failure
  if: failure()
  uses: ./.github/actions/log-to-parseable
  with:
    parseable_url: ${{ secrets.PARSEABLE_URL }}
    parseable_auth: ${{ secrets.PARSEABLE_AUTH }}
    status: failure
    message: Build failed

Method 2: OpenTelemetry Tracing

Use OpenTelemetry to trace your CI/CD pipelines.

Workflow with OTEL

name: CI Pipeline with Tracing

on:
  push:
    branches: [main]

env:
  OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.PARSEABLE_OTEL_ENDPOINT }}
  OTEL_EXPORTER_OTLP_HEADERS: "Authorization=Basic ${{ secrets.PARSEABLE_AUTH }},X-P-Stream=github-actions-traces"

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup OTEL CLI
        run: |
          curl -L https://github.com/equinix-labs/otel-cli/releases/latest/download/otel-cli-linux-amd64 -o otel-cli
          chmod +x otel-cli
          sudo mv otel-cli /usr/local/bin/
      
      - name: Build with tracing
        run: |
          otel-cli exec \
            --name "build" \
            --service "github-actions" \
            --attrs "repository=${{ github.repository }},workflow=${{ github.workflow }}" \
            -- npm run build

Method 3: Webhook Integration

Use GitHub webhooks to capture workflow events.

Webhook Receiver

Create a webhook receiver that forwards events to Parseable:

// github-webhook-to-parseable.js
const express = require('express');
const crypto = require('crypto');
const axios = require('axios');

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.GITHUB_WEBHOOK_SECRET;
const PARSEABLE_URL = process.env.PARSEABLE_URL;
const PARSEABLE_AUTH = process.env.PARSEABLE_AUTH;

function verifySignature(payload, signature) {
  const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
  const digest = 'sha256=' + hmac.update(JSON.stringify(payload)).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
}

app.post('/webhook', async (req, res) => {
  const signature = req.headers['x-hub-signature-256'];
  
  if (!verifySignature(req.body, signature)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  const event = req.headers['x-github-event'];
  const payload = req.body;
  
  // Only process workflow events
  if (event === 'workflow_run' || event === 'workflow_job') {
    const logEntry = {
      timestamp: new Date().toISOString(),
      event_type: event,
      action: payload.action,
      repository: payload.repository?.full_name,
      workflow: payload.workflow?.name || payload.workflow_run?.name,
      run_id: payload.workflow_run?.id || payload.workflow_job?.run_id,
      conclusion: payload.workflow_run?.conclusion || payload.workflow_job?.conclusion,
      actor: payload.sender?.login,
      url: payload.workflow_run?.html_url || payload.workflow_job?.html_url
    };
    
    try {
      await axios.post(`${PARSEABLE_URL}/api/v1/ingest`, [logEntry], {
        headers: {
          'Authorization': `Basic ${PARSEABLE_AUTH}`,
          'X-P-Stream': 'github-webhooks',
          'Content-Type': 'application/json'
        }
      });
    } catch (error) {
      console.error('Error sending to Parseable:', error);
    }
  }
  
  res.status(200).json({ status: 'received' });
});

app.listen(3000, () => {
  console.log('GitHub webhook receiver listening on port 3000');
});

Configure GitHub Webhook

  1. Go to your repository SettingsWebhooks
  2. Click Add webhook
  3. Set Payload URL to your webhook receiver
  4. Set Content type to application/json
  5. Set Secret to your webhook secret
  6. Select events: Workflow runs and Workflow jobs

Querying GitHub Actions Logs

Query your CI/CD logs in Parseable:

-- Get recent workflow runs
SELECT timestamp, repository, workflow, status, actor
FROM "github-actions"
ORDER BY timestamp DESC
LIMIT 100

-- Find failed builds
SELECT timestamp, repository, workflow, run_id, message
FROM "github-actions"
WHERE status = 'failure'
ORDER BY timestamp DESC

-- Build success rate by repository
SELECT 
  repository,
  COUNT(*) as total_runs,
  SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successful,
  ROUND(SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END)::float / COUNT(*) * 100, 2) as success_rate
FROM "github-actions"
WHERE timestamp > NOW() - INTERVAL '7 days'
GROUP BY repository
ORDER BY success_rate ASC

GitHub Secrets Configuration

Add these secrets to your repository:

SecretDescription
PARSEABLE_URLYour Parseable instance URL
PARSEABLE_AUTHBase64 encoded username:password
PARSEABLE_OTEL_ENDPOINTOTEL endpoint (for tracing)

Best Practices

  1. Use Secrets - Never hardcode credentials in workflows
  2. Log on Failure - Always capture failure information
  3. Include Context - Add repository, workflow, and run details
  4. Use Consistent Streams - Organize logs by dataset
  5. Set Retention - Configure appropriate log retention

Troubleshooting

Logs Not Appearing

  1. Verify Parseable URL is accessible from GitHub runners
  2. Check authentication credentials are correct
  3. Verify the dataset exists or auto-creation is enabled
  4. Check workflow logs for curl errors

Authentication Failures

  1. Verify Base64 encoding of credentials
  2. Check the Parseable user has ingest permissions
  3. Verify secrets are properly configured

Next Steps

Was this page helpful?

On this page