Core Concepts

Before integrating ExeWatch, it's important to understand how data is organized in the platform.

Application

An Application represents your software product. For each app you want to monitor, you create one Application in ExeWatch.

Examples:
- "MyDesktopApp"
- "CustomerPortal"
- "BackOffice"

Customer

A Customer is your end-user or client company. If you sell software to multiple businesses, each one is a separate Customer.

Examples:
- "ACME Corporation"
- "Smith & Associates"
- "john@example.com"

Device

A Device is a single installation of your app. Each computer, server, or browser where your app runs is a Device.

Examples:
- "john@WORKSTATION-01"
- "admin@SERVER-PROD"
- Browser fingerprint

Data Hierarchy

Application
Customers
Devices
Log Events

Each Application has multiple Customers. Each Customer has multiple Devices. Each Device sends Log Events.


Quick Start

Get up and running in 5 minutes with these simple steps.

Step-by-Step Setup
1
Create an Account

Register for free - no credit card required.

2
Create an Application

In the dashboard, click "New Application" and give it a name.

3
Get Your API Key

Click "API Keys" to see your key and download the integration code.

4
Integrate the SDK

Follow the guide below for your platform (Delphi, .NET, JavaScript, or Python). Check out the official sample projects for ready-to-run demos.

5
Start Monitoring!

Run your app and watch the logs appear in real-time.

What You'll Need
  • An ExeWatch account (free tier available)
  • Your application source code
  • For Delphi: Delphi 10 Seattle or later
  • For .NET: .NET 8.0+ on Windows
  • For JavaScript: Any modern browser
  • For Python: Python 3.7+ (no dependencies)
  • 5 minutes of your time

Delphi SDK Integration

Complete guide to integrating ExeWatch into your Delphi applications.

Step 1 Download SDK for Your App Type

Go to your Application's API Keys page to get your API Key and download the SDK. Choose the files based on your application type:

Console / Service / Web Server

Servers, CLI tools, background services

ExeWatchSDKv1.pas
VCL GUI Application (both files)

Windows desktop apps with VCL

ExeWatchSDKv1.pas + ExeWatchSDKv1.VCL.pas
FMX / Android (both files)

Multi-platform GUI apps, Android

ExeWatchSDKv1.pas + ExeWatchSDKv1.FMX.pas
Compatibility: Delphi XE8 and later (VCL, FMX, console — Windows, Linux, Android). The VCL/FMX units enable automatic unhandled exception capture for GUI apps.
Older versions or other languages? Use the DLL SDK — same features, zero compiler requirements. Verified with Delphi 5–13, C++Builder, Microsoft Visual C++ (MSVC), and Python ctypes; expected to work with MinGW, Clang, Rust, Go, C# P/Invoke, VBA, and any Windows-FFI-capable runtime. Full MSVC + C++Builder + Delphi samples at github.com/danieleteti/exewatchsamples.
Step 2 Initialize at Startup

Call InitializeExeWatch once when your application starts. The SDK works with any type of Delphi application: VCL, FMX, console, web servers, Windows services.

Recommended approach: Initialize early with empty customer ID, then set it after reading from config/license file. This ensures you capture startup logs even before knowing the customer.

uses
  ExeWatchSDKv1;  // + ExeWatchSDKv1.VCL for VCL GUI apps

begin
  // Initialize early with empty customer ID
  InitializeExeWatch('ew_win_your_api_key', '');

  EW.Info('Application starting...', 'Startup');

  // Later, after reading license/config file:
  CustomerId := LoadCustomerIdFromLicense();  // your function
  EW.SetCustomerId(CustomerId);

  EW.Info('License validated for: ' + CustomerId, 'License');
end.

For console applications, batch processors, or CLI tools. Call EW.WaitForSending before exiting — the process may terminate before the shipper thread has had time to send everything.

program MyConsoleApp;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  ExeWatchSDKv1;

begin
  try
    InitializeExeWatch('ew_win_your_api_key', '');

    // Read customer from command line or config
    if ParamCount >= 1 then
      EW.SetCustomerId(ParamStr(1));

    EW.Info('Processing started', 'Main');

    // Your application logic here...

    EW.Info('Processing completed', 'Main');
  except
    on E: Exception do
      EW.Fatal(E.Message, 'Main');
  end;

  // Wait for the shipper to drain the queue before the process exits
  EW.WaitForSending(15);
  FinalizeExeWatch;
end.

For web applications (DMVCFramework, Horse, mORMot, etc.). Initialize once at server startup.

program MyWebServer;

{$APPTYPE CONSOLE}

uses
  MVCFramework.Console,
  Web.WebReq,
  ExeWatchSDKv1,
  WebModuleU in 'WebModuleU.pas';

begin
  // Initialize ExeWatch - customer ID is your server/instance name
  InitializeExeWatch('ew_lin_your_api_key', 'production-server-01');

  EW.Info('Web server starting on port 8080', 'Startup');

  RunServer(8080);
end.

// In your controller:
procedure TMyController.GetCustomers;
begin
  EW.StartTiming('get_customers', 'api');
  try
    // Your logic...
    Render(CustomerService.GetAll);
    EW.EndTiming('get_customers');
  except
    on E: Exception do
    begin
      EW.EndTiming('get_customers',
        TJSONObject.Create(TJSONPair.Create('error', E.Message)), False);
      raise;
    end;
  end;
end;

For Windows services. Initialize in ServiceStart event.

procedure TMyService.ServiceStart(Sender: TService; var Started: Boolean);
begin
  // Initialize ExeWatch - customer ID from registry or config
  InitializeExeWatch('ew_win_your_api_key', '');
  EW.SetCustomerId(ReadCustomerFromRegistry);

  EW.Info('Service started', 'Service');
  Started := True;
end;

procedure TMyService.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
  EW.Info('Service stopping', 'Service');
  Stopped := True;
  // SDK shutdown is automatic
end;

procedure TMyService.Timer1Timer(Sender: TObject);
begin
  EW.StartTiming('background_job');
  try
    ProcessPendingTasks;
    EW.Info('Processed %d tasks', [TaskCount], 'Worker');
    EW.EndTiming('background_job');
  except
    on E: Exception do
    begin
      EW.EndTiming('background_job',
        TJSONObject.Create(TJSONPair.Create('error', E.Message)), False);
      EW.Error('Job failed: %s', [E.Message], 'Worker');
    end;
  end;
end;
Customer ID: Use something that identifies your customer uniquely - company name, license key, or internal ID. You can set it at initialization or later with EW.SetCustomerId(). For server applications, use the server/instance name instead.
Step 3 Start Logging

Use the global EW object to log messages anywhere in your application.

uses
  ExeWatchSDKv1;

procedure TMainForm.ButtonClick(Sender: TObject);
begin
  // Log different severity levels
  EW.Debug('Button clicked', 'UI');
  EW.Info('Processing started', 'Core');
  EW.Warning('Low memory detected', 'System');
  EW.Error('Database connection failed', 'DB');
  EW.Fatal('Critical error - shutting down', 'Core');
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  // Set user identity (optional but recommended)
  EW.SetUser('user123', 'john@acme.com', 'John Doe');

  EW.Info('Application started', 'Startup');
end;
That's it!

Your Delphi application is now sending logs to ExeWatch. Open the dashboard to see your logs in real-time. The SDK automatically handles offline storage, retries, hardware info collection, and exception capture.

Want more? Check out Breadcrumbs to capture the trail of user actions before an error, Performance Timing to measure operation durations, and Custom Metrics to track counters and gauges.

Runnable samples: DelphiVCL (end-to-end demo), SpecificScenarios/ (focused how-tos: breadcrumbs, madExcept integration, initial device info), DelphiWebBroker & DelphiDMVCFramework (server-side).


.NET / C# SDK Integration

Add monitoring to your .NET applications — WinForms, WPF, console apps, and Windows services. Requires .NET 8.0+ on Windows.

Installation

Add the ExeWatch project reference to your solution. For WinForms apps, also add ExeWatch.WinForms. Download both from the SDK Downloads page.

Initialization

Initialize the SDK at application startup:

using ExeWatch;

// Console / WPF / Service
ExeWatchSdk.Initialize(new ExeWatchConfig
{
    ApiKey = "ew_win_your_key",
    CustomerId = "ACME-Corp"
});

// WinForms - also install the GUI exception hook
using ExeWatch.WinForms;
ExeWatchWinForms.Install(); // before Application.Run()

// Console / CLI apps: wait for the shipper to drain before exiting
EW.WaitForSending(15);
ExeWatchSdk.Shutdown();

Usage

Use the EW shortcut class anywhere in your code:

using ExeWatch;

// Logging
EW.Info("User logged in", "auth");
EW.Error("Connection failed", "database");

// User identity
EW.SetUser("user123", "john@acme.com", "John Doe");

// Global tags
EW.SetTag("environment", "production");

// Breadcrumbs
EW.AddBreadcrumb("Opened Settings", "navigation");

// Performance timing with try/catch
EW.StartTiming("load_customers", "database");
try
{
    var data = LoadCustomers();
    EW.EndTiming("load_customers");
}
catch (Exception ex)
{
    EW.EndTiming("load_customers",
        new Dictionary<string, object> { ["error"] = ex.Message }, false);
    throw;
}

// Custom metrics
EW.IncrementCounter("orders_processed");
EW.SetGauge("queue_length", 42);

Features

  • Automatic Exception Capture — Unhandled exceptions, WinForms thread exceptions
  • Offline Persistence — Logs saved to disk, sent when connection is available
  • Hardware Info — CPU, RAM, disks, monitors, OS version
  • User Identity — Track users across sessions
  • Breadcrumbs — Trail of user actions before errors
  • Performance Timing — Measure any operation with StartTiming/EndTiming
  • Custom Metrics — Counters and gauges with periodic sampling
  • Global Tags — Key-value context for all logs
  • Server Config — Remotely adjust flush interval, batch size, log level, sampling
  • Zero Dependencies — No NuGet packages required, uses System.Text.Json and System.Net.Http

Supported App Types

App Type Package Exception Capture
Console ExeWatch AppDomain.UnhandledException
Windows Service ExeWatch AppDomain.UnhandledException
WinForms ExeWatch + ExeWatch.WinForms Application.ThreadException + AppDomain
WPF ExeWatch AppDomain.UnhandledException

JavaScript SDK Integration

Add monitoring to your web applications in just a few lines of code.

Step 1 Add the Configuration

Add the configuration object to your HTML page, before the SDK script. You'll find your API key in the dashboard under API Keys > JavaScript.

<!-- Add this before </body> -->
<script>
  window.ewConfig = {
    apiKey: 'ew_web_xxxxxxxxxxxxxxxx',
    customerId: 'ACME-Corporation'
  };
</script>
Step 2 Include the SDK

Add the SDK script right after the configuration. Use the minified version for production.

<!-- Production (minified, 12KB) -->
<script src="https://exewatch.com/static/js/exewatch.v1.min.js"></script>

<!-- Development (readable, 35KB) -->
<script src="https://exewatch.com/static/js/exewatch.v1.js"></script>
Step 3 Start Logging

Use the global ew object anywhere in your JavaScript code (same as Delphi's EW).

// Set user identity (optional but recommended)
ew.setUser({ id: 'user123', email: 'john@acme.com', name: 'John Doe' });

// Log messages
ew.debug('Page loaded', 'init');
ew.info('User clicked checkout', 'ui');
ew.warning('Slow API response', 'api');
ew.error('Payment failed', 'checkout');

// Add breadcrumbs for debugging
ew.addBreadcrumb('Clicked Buy Now button', 'ui');
ew.addBreadcrumb('Navigated to /checkout', 'router');

// Set global tags
ew.setTag('environment', 'production');
ew.setTag('version', '2.5.0');
Complete Example
<!DOCTYPE html>
<html>
<head>
  <title>My Web App</title>
</head>
<body>
  <h1>Welcome</h1>
  <button onclick="doSomething()">Click me</button>

  <!-- ExeWatch Configuration -->
  <script>
    window.ewConfig = {
      apiKey: 'ew_web_xxxxxxxxxxxxxxxx',
      customerId: 'ACME-Corporation'
    };
  </script>
  <script src="https://exewatch.com/static/js/exewatch.v1.min.js"></script>

  <script>
    // Initialize user
    ew.setUser({ id: 'user123', email: 'john@acme.com' });
    ew.info('Page loaded', 'init');

    function doSomething() {
      ew.addBreadcrumb('Button clicked', 'ui');
      ew.info('User clicked the button', 'ui');
    }
  </script>
</body>
</html>
Filtering Third-Party Errors (ignoreUrls)

By default, the SDK captures all errors on the page, including failures from third-party scripts (Google Ads, analytics, fonts, social widgets). Use ignoreUrls to filter out noise from URLs you don't control.

<script>
  window.ewConfig = {
    apiKey: 'ew_web_xxxxxxxxxxxxxxxx',
    customerId: 'ACME-Corporation',
    ignoreUrls: [
      // Strings: match if URL contains the string
      'googlesyndication.com',
      'googleadservices.com',
      'google-analytics.com',
      'googletagmanager.com',
      'fonts.googleapis.com',
      'facebook.net',
      'doubleclick.net',

      // RegExp: for more complex patterns
      /analytics\..*\.com/,
      /cdn\.third-party\.io/
    ]
  };
</script>
What gets filtered: Resource loading errors (<script>, <link>, <img>), Fetch/XHR network errors, and uncaught JavaScript errors originating from matching URLs. Breadcrumbs from ignored URLs are also suppressed to keep your trail clean.
Custom HTTP Error Extractor (httpErrorExtractor) Advanced · Optional
Most users don't need this. The SDK already captures HTTP error responses automatically (method, URL, status, response body up to 2 KB) — skip this option during onboarding.

Configure this only when all of the following apply:

  • Your backend returns errors in a non-standard JSON schema (not { "message": ... }, { "detail": ... } or { "error": { "message": ... } })
  • You want specific fields from that payload (e.g. exception, statuscode, traceId) promoted to the log title or to dedicated fields in extra_data for filtering
  • The default raw responseBody capture (already enabled) isn't enough for your workflow

Example — API returns { "error": { "message": "...", "exception": "..." }, "statuscode": 500 }:

window.ewConfig = {
  apiKey: 'ew_web_xxxxxxxxxxxxxxxx',
  customerId: 'ACME-Corporation',

  // Optional: parse your custom error schema
  httpErrorExtractor: function(body, contentType, status, url) {
    try {
      var j = JSON.parse(body);
      return {
        message: j.error && j.error.message,          // shown in log title
        extra: {                                                // merged into extra_data
          apiException: j.error && j.error.exception,
          apiStatusCode: j.statuscode
        }
      };
    } catch (e) { return null; }  // fall back to default parsing
  }
};
Safe by design: if your extractor throws or returns null, the SDK silently falls back to default parsing and still attaches the raw responseBody. You never lose error data.
JavaScript SDK Configuration Reference
Option Type Default Description
apiKey string required Browser API key (starts with ew_web_)
customerId string required Customer identifier
appVersion string null App version for release tracking
autoCapture boolean true Auto-capture window errors, fetch/XHR failures, console.error
ignoreUrls Array [] URL patterns (strings or RegExp) to ignore in auto-capture. Matching resource, fetch, XHR, and uncaught errors are silently dropped.
sampleRate number 1.0 Sampling rate (0.0-1.0). Errors are always captured regardless.
debug boolean false Enable SDK debug output in browser console
enabled boolean true Enable/disable the SDK
batchSize number 10 Events per batch before auto-flush
flushInterval number 5000 Auto-flush interval in milliseconds
httpErrorExtractor Advanced Function null Custom parser for non-standard HTTP error response schemas. Signature: (body, contentType, status, url) → { message?, extra? } | null. See the Custom HTTP Error Extractor section above — most users don't need this.
That's it!

Your web application is now sending logs to ExeWatch. The SDK automatically captures browser info, screen size, and generates a persistent device ID.

Want more? Check out Performance Timing to measure operation durations and Custom Metrics to track counters and gauges.

Runnable sample: ExeWatchSamples/JS.


Python SDK Integration

Add monitoring to your Python applications — Django, Flask, FastAPI, CLI tools, services, and scripts. Single-file SDK with zero external dependencies, compatible with Python 3.7+.

Download & Setup

The Python SDK is a single file (exewatch.py) with no external dependencies. Download it from your Application's API Keys page, or directly:

Download exewatch.py

Copy the file into your project directory and import it. No pip install needed.

Basic Usage
from exewatch import initialize_exewatch, ew, finalize_exewatch

# Initialize once at startup
initialize_exewatch('YOUR_API_KEY', 'YOUR_CUSTOMER_ID', app_version='1.0.0')

# Log anywhere in your application
ew.info('Application started', 'startup')
ew.warning('Disk space low', 'system')
ew.error('Database connection failed', 'db')

# Extra data via keyword arguments
ew.info('Order placed', 'orders', order_id='12345', total=99.99)

# Short-lived script: wait for the shipper to drain the queue before exiting
ew.wait_for_sending(15)
finalize_exewatch()
Exception Capture
try:
    risky_operation()
except Exception as e:
    ew.error_with_exception(e, 'module', 'Context message')
    # Captures exception class, message, and full stack trace
Timing & Metrics
# Performance timing
ew.start_timing('db_query', 'database')
result = db.execute('SELECT ...')
elapsed = ew.end_timing('db_query')  # returns ms, logs automatically

# Counters (cumulative)
ew.increment_counter('orders.placed', 1.0, 'shop')

# Gauges (instantaneous values)
ew.record_gauge('memory_mb', 256.0, 'system')

# Periodic gauge (sampled automatically in background)
def get_memory():
    import psutil
    return psutil.Process().memory_info().rss / 1024 / 1024

ew.register_periodic_gauge('memory_mb', get_memory, 'system')
User Identity & Breadcrumbs
# User identity (attached to all subsequent logs)
ew.set_user('user-42', 'jane@example.com', 'Jane Doe')

# Breadcrumbs (auto-attached to ERROR/FATAL logs)
from exewatch import BreadcrumbType

ew.add_breadcrumb(BreadcrumbType.NAVIGATION, 'nav', 'Opened settings')
ew.add_breadcrumb(BreadcrumbType.CLICK, 'ui', 'Clicked Save')
ew.add_breadcrumb(BreadcrumbType.QUERY, 'db', 'SELECT * FROM users')
# Next error() or fatal() will include these breadcrumbs

# Global tags (attached to all logs)
ew.set_tag('environment', 'production')
ew.set_tag('feature_flag', 'new_checkout')

Your Python application is now sending logs to ExeWatch. The SDK handles offline storage, background shipping, retries, and device info collection automatically.

Platform support: Windows and Linux. Thread-safe with thread-local breadcrumbs. Zero dependencies — uses only the Python 3.7+ standard library.

Runnable sample: ExeWatchSamples/Python.

Delphi 4–XE7 Integration (DLL SDK)

For Delphi 4 through Delphi XE7. Uses a pre-built DLL with a lightweight Delphi import unit — no source compilation required.

Step 1 Download the DLL SDK

Download the DLL package from your Application's API Keys page. It contains everything you need:

ExeWatchSDKv1Imports.pas — Delphi import unit (Delphi 4+, no dots in name for old compiler compatibility) ExeWatchSDKv1DLL.dll — 32-bit DLL ExeWatchSDKv1DLL_x64.dll — 64-bit DLL

Add ExeWatchSDKv1Imports.pas to your project. Copy the appropriate DLL next to your executable.

String handling: The EW* convenience wrappers (EWInfo, EWError, etc.) handle string conversion automatically for all Delphi versions. In Delphi 5–2007 (AnsiString) they convert to WideString internally — no manual casting needed.
Step 2 Initialize at Startup

Call EWInitialize once at application startup. Works with VCL apps, console programs, and Windows services.

Recommended: Initialize early with an empty customer ID, set it after reading from config or license file.

uses
  ExeWatchSDKv1Imports;

begin
  // Initialize early — empty customer ID, capture startup logs immediately
  EWInitialize('ew_win_your_api_key', '');

  EWInfo('Application starting...', 'Startup');

  // Later, after reading license / config:
  CustomerId := LoadCustomerIdFromLicense;  // your function
  EWSetCustomerId(CustomerId);

  EWInfo('License validated for: ' + CustomerId, 'License');
end.

Full VCL application example with unhandled exception capture (manual hook — the DLL version does not include the ExeWatchSDKv1.VCL unit).

uses
  ..., ExeWatchSDKv1Imports;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  if EWInitialize('ew_win_your_api_key', 'ACME-Corp') <> EW_OK then
  begin
    ShowMessage('ExeWatch init failed: ' + string(EWGetLastErrorStr));
    Application.Terminate;
    Exit;
  end;

  // Manual exception hook — replaces ExeWatchSDKv1.VCL unit
  Application.OnException := OnAppException;

  EWInfo('Application started', 'Startup');
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  ew_Shutdown;  // flush and close the SDK
end;

procedure TMainForm.OnAppException(Sender: TObject; E: Exception);
begin
  // Pass stack trace if you use JCL / madExcept / EurekaLog; otherwise empty
  EWErrorWithStackTrace(E.Message, 'exception', '', E.ClassName);
  Application.ShowException(E);
end;

Console applications, batch processors, or CLI tools. Call ew_WaitForSending before exiting — the process may terminate before the shipper thread has had time to send everything.

program MyConsoleApp;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  ExeWatchSDKv1Imports;

begin
  try
    EWInitialize('ew_win_your_api_key', '');

    if ParamCount >= 1 then
      EWSetCustomerId(ParamStr(1));

    EWInfo('Processing started', 'Main');

    // Your application logic here...

    EWInfo('Processing completed', 'Main');
  except
    on E: Exception do
      EWFatal(E.Message, 'Main');
  end;

  // Wait for the shipper to drain the queue before the process exits
  ew_WaitForSending(15);
  ew_Shutdown;
end.

Windows services. Initialize in ServiceStart, shut down in ServiceStop.

procedure TMyService.ServiceStart(Sender: TService; var Started: Boolean);
begin
  EWInitialize('ew_win_your_api_key', '');
  EWSetCustomerId(ReadCustomerFromRegistry);
  EWInfo('Service started', 'Service');
  Started := True;
end;

procedure TMyService.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
  EWInfo('Service stopping', 'Service');
  ew_Shutdown;
  Stopped := True;
end;

// Background job with timing
procedure TMyService.Timer1Timer(Sender: TObject);
var
  ElapsedMs: Double;
begin
  EWStartTiming('background_job', 'worker');
  try
    ProcessPendingTasks;
    EWEndTiming('background_job', ElapsedMs);
    EWInfo('Job completed', 'Worker');
  except
    on E: Exception do
    begin
      EWEndTiming('background_job', ElapsedMs);
      EWError('Job failed: ' + E.Message, 'Worker');
    end;
  end;
end;
Dynamic loading (optional): Add {$DEFINE EW_DYNAMIC_LOAD} to your project options before this unit. Then call EWLoadDLL at startup — if the DLL is missing the app still launches instead of failing immediately.
Step 3 Start Logging

Use the EW* convenience wrappers anywhere in your code. All accept plain Delphi strings.

// Five severity levels — same as native SDK
EWDebug('Entering payment handler', 'UI');
EWInfo('Processing started', 'Core');
EWWarning('Low memory detected', 'System');
EWError('Database connection failed', 'DB');
EWFatal('Critical failure — aborting', 'Core');

// User identity (optional)
EWSetUser('user-42', 'john@acme.com', 'John Doe');

// Breadcrumbs — attached to next Error/Fatal automatically
EWAddBreadcrumb(EW_BT_NAVIGATION, 'router', 'Opened customer details');
EWAddBreadcrumb(EW_BT_CLICK, 'ui', 'Clicked Save');
EWAddBreadcrumb(EW_BT_QUERY, 'db', 'UPDATE customers SET ...');
EWError('Save failed: connection lost', 'DB');  // breadcrumbs attached here

// Performance timing
var
  ElapsedMs: Double;
begin
  EWStartTiming('load_customers', 'database');
  try
    RunQuery;
    EWEndTiming('load_customers', ElapsedMs);
  except
    on E: Exception do
    begin
      EWEndTiming('load_customers', ElapsedMs);
      EWError('Query failed: ' + E.Message, 'DB');
    end;
  end;

// Metrics
EWIncrementCounter('orders.placed', 1, 'shop');
EWRecordGauge('active_connections', ActiveCount, 'db');

// Global tags (attached to all subsequent logs)
EWSetTag('environment', 'production');
EWSetTag('feature_flag', 'new_checkout');
Periodic Gauges: The DLL SDK cannot accept anonymous method callbacks, so RegisterPeriodicGauge is not available. Use a TTimer instead — call EWRecordGauge on each tick. Same result.
That's it!

Your legacy Delphi application is now sending logs to ExeWatch via the DLL SDK. All features are available: logging, breadcrumbs, timing, metrics, user identity, global tags, and device info.

Runnable sample: ExeWatchSamples/DelphiWithDLLSDK — complete VCL demo using every DLL SDK feature (Delphi 4–XE7 compatible).


C++Builder Integration (DLL SDK)

Add ExeWatch monitoring to C++Builder VCL applications using the C/C++ header and the dynamic loading API. The same header works with bcc32, bcc64x, MSVC, MinGW, and Clang — no import library format issues.

Step 1 Download the DLL SDK

Download the DLL package from your Application's API Keys page. Add these files to your C++Builder project:

ExeWatchSDKv1.h — C/C++ header with all function declarations and types ExeWatchSDKv1.dynload.c — dynamic loader implementation (add as a project unit) ExeWatchSDKv1DLL.dll — 32-bit DLL (copy next to your executable) ExeWatchSDKv1DLL_x64.dll — 64-bit DLL
Dynamic loading is strongly recommended for C++Builder. It avoids every import-library format mismatch (.lib vs .a vs COFF vs OMF) and lets the app start gracefully even if the DLL is missing. Just define EW_DYNAMIC_LOAD before the #include and add ExeWatchSDKv1.dynload.c to the project.
Step 2 Initialize at Startup

Include the header with EW_DYNAMIC_LOAD, load the DLL, then call ew_Initialize. All string arguments use wide literals (L"...").

Full VCL application example with dynamic DLL loading and unhandled exception capture.

// In your .cpp file — BEFORE any other ExeWatch include
#define EW_DYNAMIC_LOAD
#include "ExeWatchSDKv1.h"
// Also add ExeWatchSDKv1.dynload.c to the project

static const wchar_t* EXEWATCH_API_KEY = L"ew_win_your_api_key";

void __fastcall TMainForm::FormCreate(TObject *Sender)
{
    // Load the DLL (searches exe dir and Windows DLL path)
    if (ew_LoadSDK() != EW_OK) {
        ShowMessage("Failed to load ExeWatch DLL");
        Application->Terminate();
        return;
    }

    // Initialize the SDK
    if (ew_Initialize(EXEWATCH_API_KEY, L"ACME-Corp", L"") != EW_OK) {
        wchar_t errBuf[512];
        ew_GetLastError(errBuf, 512);
        ShowMessage(String("ExeWatch init failed: ") + errBuf);
        Application->Terminate();
        return;
    }

    // Hook unhandled VCL exceptions
    Application->OnException = OnAppException;

    ew_Info(L"Application started", L"startup");
}

void __fastcall TMainForm::FormDestroy(TObject *Sender)
{
    if (ew_IsSDKLoaded()) {
        ew_Shutdown();
        ew_UnloadSDK();
    }
}

void __fastcall TMainForm::OnAppException(TObject *Sender, Exception *E)
{
    String msg = String(E->ClassName()) + ": " + E->Message;
    ew_ErrorWithStackTrace(msg.c_str(), L"exception", NULL, E->ClassName().c_str());
    Application->ShowException(E);
}

Console or batch applications. Use a standard main entry point. Call ew_WaitForSending before exiting — the process may terminate before the shipper thread has had time to send everything.

#define EW_DYNAMIC_LOAD
#include "ExeWatchSDKv1.h"

int main()
{
    if (ew_LoadSDK() != EW_OK) return 1;

    ew_Initialize(L"ew_win_your_api_key", L"ACME-Corp", L"");
    ew_Info(L"Processing started", L"main");

    // Your logic...

    ew_Info(L"Processing completed", L"main");

    // Wait for the shipper to drain the queue before the process exits
    ew_WaitForSending(15);
    ew_Shutdown();
    ew_UnloadSDK();
    return 0;
}

Windows services. Initialize in ServiceStart and shut down in ServiceStop.

void __fastcall TMyService::ServiceStart(TService *Sender, bool &Started)
{
    if (ew_LoadSDK() != EW_OK) { Started = false; return; }

    ew_Initialize(L"ew_win_your_api_key", L"", L"");
    ew_SetCustomerId(ReadCustomerFromRegistry());
    ew_Info(L"Service started", L"service");
    Started = true;
}

void __fastcall TMyService::ServiceStop(TService *Sender, bool &Stopped)
{
    ew_Info(L"Service stopping", L"service");
    ew_Shutdown();
    ew_UnloadSDK();
    Stopped = true;
}

// Background job with timing
void __fastcall TMyService::Timer1Timer(TObject *Sender)
{
    double elapsed;
    ew_StartTiming(L"background_job", L"worker");
    try {
        ProcessPendingTasks();
        ew_EndTiming(L"background_job", &elapsed);
        ew_Info(L"Job completed", L"worker");
    } catch (Exception &E) {
        ew_EndTiming(L"background_job", &elapsed);
        ew_Error(E.Message.c_str(), L"worker");
    }
}
Step 3 Logging, Breadcrumbs, Timing & Metrics

All ew_* functions accept wchar_t* (wide strings). Use L"..." literals or UnicodeString::c_str().

// Five logging levels
ew_Debug(L"Entering payment handler", L"ui");
ew_Info(L"Processing started", L"core");
ew_Warning(L"Low memory detected", L"system");
ew_Error(L"Database connection failed", L"db");
ew_Fatal(L"Critical failure — aborting", L"core");

// User identity (optional)
ew_SetUser(L"user-42", L"john@acme.com", L"John Doe");

// Breadcrumbs — auto-attached to the next Error/Fatal
ew_AddBreadcrumb(EW_BT_NAVIGATION, L"router", L"Opened customer details", NULL);
ew_AddBreadcrumb(EW_BT_CLICK,      L"ui",     L"Clicked Save", NULL);
ew_AddBreadcrumb(EW_BT_QUERY,      L"db",     L"UPDATE customers SET ...", NULL);
ew_Error(L"Save failed: connection lost", L"db");  // breadcrumbs attached here

// Performance timing
double elapsed;
ew_StartTiming(L"load_customers", L"database");
try {
    RunQuery();
    ew_EndTiming(L"load_customers", &elapsed);
} catch (Exception &E) {
    ew_EndTiming(L"load_customers", &elapsed);
    ew_Error(E.Message.c_str(), L"db");
}

// Counters (cumulative)
ew_IncrementCounter(L"orders.placed", 1.0, L"shop");

// Gauges (point-in-time)
ew_RecordGauge(L"active_connections", (double)ActiveCount, L"db");

// Global tags
ew_SetTag(L"environment", L"production");
ew_SetTag(L"feature_flag", L"new_checkout");
Periodic Gauges: The DLL SDK does not support function-pointer callbacks for periodic sampling. Use a TTimer and call ew_RecordGauge on each tick — identical result.
That's it!

Your C++Builder application is now sending logs to ExeWatch. All features work: logging, breadcrumbs, timing, metrics, user identity, global tags, and device info.

Runnable sample: ExeWatchSamples/CPPBuilderWithDLLSDK — complete VCL demo covering every feature (bcc32 and bcc64x). Also see MSVCWithDLLSDK for a pure C MSVC console example using the same header.


MSVC / C Integration (DLL SDK)

Add ExeWatch monitoring to any Windows C or C++ application built with Microsoft Visual C++ (cl.exe), MinGW, Clang, or any toolchain that follows the Windows stdcall ABI. The same two files — header + dynamic loader — compile unchanged on all of them.

Step 1 Download the DLL SDK

Download the DLL package from your Application's API Keys page and add these files to your project:

ExeWatchSDKv1.h — C/C++ header (function declarations, types, constants) ExeWatchSDKv1.dynload.c — dynamic loader (compile alongside your source — no linker step needed) ExeWatchSDKv1DLL_x64.dll — 64-bit DLL (copy next to your executable) ExeWatchSDKv1DLL.dll — 32-bit DLL

Compile the loader alongside your source. No import library (.lib) required — dynload.c resolves everything at runtime via LoadLibrary + GetProcAddress.

MSVC build command:
cl /EHsc /W4 /nologo /I<sdk-dir> myapp.c <sdk-dir>\ExeWatchSDKv1.dynload.c
Step 2 Initialize at Startup

Define EW_DYNAMIC_LOAD before the header, call ew_LoadSDK() once, then ew_Initialize(). All string arguments use wide literals (L"...").

Console or batch applications. Call ew_WaitForSending before exiting — the process may terminate before the shipper thread has had time to send everything.

#define EW_DYNAMIC_LOAD
#include "ExeWatchSDKv1.h"
#include <cstdio>
// Compile with: cl /EHsc myapp.c ExeWatchSDKv1.dynload.c

int wmain()
{
    // Load DLL at runtime — no import library needed
    if (ew_LoadSDK() != EW_OK) {
        fwprintf(stderr, L"Failed to load ExeWatch DLL\n");
        return 1;
    }

    if (ew_Initialize(L"ew_win_your_api_key", L"ACME-Corp", L"1.0.0") != EW_OK) {
        wchar_t err[512] = {};
        ew_GetLastError(err, 512);
        fwprintf(stderr, L"Init failed: %ls\n", err);
        ew_UnloadSDK();
        return 1;
    }

    ew_Info(L"Processing started", L"main");

    /* your application logic here */

    ew_Info(L"Processing completed", L"main");

    // Wait for the shipper to drain the queue before the process exits
    int remaining = ew_WaitForSending(15);
    if (remaining > 0)
        fwprintf(stderr, L"%d event(s) still queued — will retry on next run\n", remaining);

    ew_Shutdown();
    ew_UnloadSDK();
    return 0;
}

Recommended: Initialize early with an empty customer ID, set it after reading from config or license file.

#define EW_DYNAMIC_LOAD
#include "ExeWatchSDKv1.h"

ew_LoadSDK();

// Initialize early — empty customer ID, capture startup events immediately
ew_Initialize(L"ew_win_your_api_key", L"", L"1.0.0");

ew_Info(L"Application starting...", L"startup");

// Later, after reading license / config:
wchar_t customerId[256];
LoadCustomerIdFromLicense(customerId, 256);  /* your function */
ew_SetCustomerId(customerId);

ew_Info(L"License validated", L"license");

Windows services. Initialize in ServiceMain, shut down in the stop handler.

VOID WINAPI ServiceMain(DWORD argc, LPTSTR* argv)
{
    /* register service control handler, set status to START_PENDING ... */

    ew_LoadSDK();
    ew_Initialize(L"ew_win_your_api_key", L"", L"1.0.0");
    ew_SetCustomerId(ReadCustomerFromRegistry());
    ew_Info(L"Service started", L"service");

    /* set status to RUNNING, enter main service loop */
    ServiceLoop();

    ew_Info(L"Service stopping", L"service");
    ew_Shutdown();
    ew_UnloadSDK();
}

// Timed background job
void RunBackgroundJob()
{
    double elapsed;
    ew_StartTiming(L"background_job", L"worker");
    if (ProcessPendingTasks() == 0) {
        ew_EndTiming(L"background_job", &elapsed);
        ew_Info(L"Job completed", L"worker");
    } else {
        ew_EndTiming(L"background_job", &elapsed);
        ew_Error(L"Job failed", L"worker");
    }
}
Step 3 Logging, Breadcrumbs, Timing & Metrics

All ew_* functions accept wchar_t*. Use L"..." literals or wide string buffers. Works identically across MSVC, MinGW, and Clang.

// Five logging levels
ew_Debug(L"Entering payment handler", L"ui");
ew_Info(L"Processing started", L"core");
ew_Warning(L"Low memory detected", L"system");
ew_Error(L"Database connection failed", L"db");
ew_Fatal(L"Critical failure", L"core");

// User identity (optional)
ew_SetUser(L"user-42", L"alice@acme.com", L"Alice Smith");

// Breadcrumbs — auto-attached to the next Error/Fatal
ew_AddBreadcrumb(EW_BT_NAVIGATION, L"nav",    L"Opened main screen",      NULL);
ew_AddBreadcrumb(EW_BT_CLICK,      L"ui",     L"Clicked 'Run report'",    NULL);
ew_AddBreadcrumb(EW_BT_QUERY,      L"db",     L"SELECT * FROM customers", NULL);
ew_Error(L"Save failed: connection lost", L"db");  // breadcrumbs attached here

// Performance timing
double elapsed;
ew_StartTiming(L"generate_report", L"reports");
GenerateReport();
ew_EndTiming(L"generate_report", &elapsed);
wprintf(L"Report done in %.1f ms\n", elapsed);

// Counters (cumulative) and gauges (point-in-time)
ew_IncrementCounter(L"reports.generated", 1.0, L"reports");
ew_RecordGauge(L"queue.depth", (double)QueueSize(), L"jobs");

// Global tags (attached to all subsequent events)
ew_SetTag(L"environment", L"production");
ew_SetTag(L"build",       L"msvc-x64");
That's it!

Your MSVC application is now sending logs to ExeWatch. The same code compiles unchanged with MinGW, Clang, and any other Windows C/C++ toolchain — no changes to the SDK files required.

Runnable sample: ExeWatchSamples/MSVCWithDLLSDK — complete console demo with every feature, includes run_msvc.cmd to build and run in one step.


Initialization Options

The SDK supports multiple initialization modes to fit different scenarios.

Standard Initialization

The simplest way to initialize - provide API key and Customer ID at startup.

// Delphi
Config := TExeWatchConfig.Create('ew_desk_xxx', 'ACME-Corp');
InitializeExeWatch(Config);
EW.SendDeviceInfo;
Deferred Customer ID

Initialize the SDK without knowing the customer ID upfront. This is useful when:

  • License validation happens after app startup
  • User authentication determines the customer
  • You want to track unlicensed installations
// Initialize without customer ID
Config := TExeWatchConfig.Create('ew_desk_xxx', '');  // Empty customer_id
InitializeExeWatch(Config);

// Logs are tracked by device_id until customer is set
EW.Warning('No valid license found', 'license');

// Later, after license validation...
EW.SetCustomerId('ACME-Corp');
EW.Info('License validated successfully', 'license');
When no Customer ID is set, logs are associated with the Device ID (e.g., "john@PC-WORK"). This lets you track unlicensed installations.
Configuration Options

The TExeWatchConfig record supports many options:

Option Default Description
Release (empty) Version tag for release tracking
AppVersion (auto-detect) App version (auto-detected from executable)
SampleRate 1.0 Sampling rate (0.0-1.0). 1.0 = 100% of events
BufferSize 100 Max events before auto-flush
FlushIntervalMs 5000 Auto-flush interval in milliseconds
RetryIntervalMs 30000 Wait time between retry attempts
StoragePath %LOCALAPPDATA%\ExeWatch Path for offline log storage
// Full configuration example
Config := TExeWatchConfig.Create('ew_desk_xxx', 'ACME-Corp');
Config.Release := '2.5.0';
Config.SampleRate := 0.5;  // Send 50% of debug/info events
Config.BufferSize := 50;
Config.FlushIntervalMs := 3000;
InitializeExeWatch(Config);

Advanced Customer ID Scenarios

Real-world examples of handling customer identification in different application types.

Customer ID Change at Runtime

Use case: User switches between different accounts/licenses without restarting the application.

procedure TMainForm.FormCreate(Sender: TObject);
var
  Config: TExeWatchConfig;
begin
  Config := TExeWatchConfig.Create('ew_win_abc123...', 'CUSTOMER-A');
  InitializeExeWatch(Config);
  // Device info sent with customer_id = 'CUSTOMER-A'
end;

procedure TMainForm.OnUserSwitched(const NewCustomer: string);
begin
  // User switches from CUSTOMER-A to CUSTOMER-B
  EW.SetCustomerId(NewCustomer);

  // Device info is RE-SENT with the new customer_id
  // Backend updates the device to belong to the new customer
  EW.Info('User switched to ' + NewCustomer, 'auth');
end;
How it works: When you call SetCustomerId with a different customer_id, the SDK automatically re-sends device info with the new customer. The device in the backend is moved to the new customer.
Multi-Tenant Desktop Application

Use case: Desktop SaaS application where customer_id is determined after user login.

var
  FCurrentCustomer: string = '';

procedure TMainForm.FormCreate(Sender: TObject);
var
  Config: TExeWatchConfig;
begin
  // Initialize SDK WITHOUT customer_id
  Config := TExeWatchConfig.Create('ew_win_abc123...', '');
  InitializeExeWatch(Config);

  // Log without customer_id (user not logged in yet)
  EW.Info('Application started - waiting for login', 'startup');
end;

procedure TMainForm.OnLoginSuccess(const Username, Company: string);
begin
  // After login, set customer_id
  FCurrentCustomer := Company;
  EW.SetCustomerId(FCurrentCustomer);

  // From now on, all logs have customer_id
  EW.Info('User logged in: ' + Username, 'auth');
end;

procedure TMainForm.OnLogout;
begin
  EW.Info('User logged out: ' + FCurrentCustomer, 'auth');
  // Optionally reset customer_id for next user
  // EW.SetCustomerId('');
end;
Tip: Logs sent before SetCustomerId are associated with the device ID only (e.g., "john@PC-WORK"). This helps track pre-login activity and unlicensed installations.
Custom Deployment Information

Use case: Track custom deployment metadata like environment (production/staging), database version, or feature flags.

procedure TMainForm.FormCreate(Sender: TObject);
var
  Config: TExeWatchConfig;
  Environment, DBVersion: string;
begin
  // Read configuration from file/registry
  Environment := ReadEnvironmentFromConfig;  // 'production', 'staging', etc.
  DBVersion := GetDatabaseVersion;           // '5.2.1'

  Config := TExeWatchConfig.Create('ew_win_abc123...', 'CUSTOMER-ID');

  // Add custom device info (array of TPair)
  Config.InitialCustomDeviceInfo := [
    TPair<string, string>.Create('environment', Environment),
    TPair<string, string>.Create('db_version', DBVersion),
    TPair<string, string>.Create('deployment_region', 'EU-West')
  ];

  InitializeExeWatch(Config);
  EW.Info('Application started in ' + Environment, 'startup');
end;

// Update custom info at runtime
procedure TMainForm.OnFeatureFlagChanged(const Flags: string);
begin
  EW.SetCustomDeviceInfo('feature_flags', Flags);
  // Changes sent on next log batch
end;
Result: In the backend device modal, you'll see your custom info alongside hardware details: environment: production, db_version: 5.2.1, deployment_region: EU-West.
More details: For complete integration patterns, troubleshooting, and best practices, see the Delphi SDK Integration Guide

Release Tracking

Track which version of your application generated each log event.

What is Release?

Release is a version identifier that tags every log event with the version of your app that generated it. This lets you correlate errors and issues with specific versions of your software.

Without Release

"We're seeing 50 errors today. Is this a new bug? Was it introduced in the last update? Did we already fix it?"

With Release

"50 errors - all from version 2.4.0. Version 2.5.0 has zero errors. The fix in 2.5.0 worked!"

Release vs AppVersion

The SDK tracks two version-related values. Understanding the difference helps you use them correctly:

Release AppVersion
What is it A label you choose to identify this deployment/build The version number from the executable's metadata
How it's set You set it manually via Config.Release Auto-detected from executable (Project > Options > Version Info)
Example "2.5.0", "2024.01.15", "abc123" "2.5.0.1234" (FileVersion from EXE)
When to use Track deployments, hotfixes, feature branches Track exact build numbers
Recommendation: Set Release to your semantic version (e.g., "2.5.0") and let AppVersion be auto-detected for the full build number.
How to Set the Release
Delphi
// Set release at initialization
Config := TExeWatchConfig.Create(
  'ew_desk_xxx',
  'ACME-Corp'
);
Config.Release := '2.5.0';
InitializeExeWatch(Config);

// All logs now tagged with release=2.5.0
EW.Info('App started', 'startup');
JavaScript
// Set release in config
window.ewConfig = {
  apiKey: 'ew_web_xxx',
  customerId: 'ACME-Corp',
  release: '2.5.0'  // Version tag
};

// All logs now tagged with release=2.5.0
ew.info('Page loaded', 'init');
What Value Should I Use?

Choose a format that works for your release process:

Format Example Best for
Semantic Version "2.5.0", "1.0.0-beta" Most applications - clear major.minor.patch numbering
Date-based "2024.01.15", "24.1" Continuous deployment, monthly releases
Git commit "abc123f", "main-abc123" Frequent deployments, when you need exact code reference
Build number "build-1234", "CI-5678" CI/CD pipelines with incrementing build IDs
Why Track Releases?
  • Identify regressions - See if errors started after a specific release
  • Verify fixes - Confirm that a bug fix actually reduced errors
  • Rollback decisions - Know which version to rollback to if needed
  • Gradual rollout - Monitor errors as you deploy to more users
  • Compare versions - See if v2.5 is more stable than v2.4
  • Support - Ask "what version are you on?" when helping users

Updates Page

Monitor which versions are deployed across your devices and track adoption.

How it Works

The Updates page shows the current version state of every device in your application. Every time a device sends data to ExeWatch (logs, device info), its version information is automatically updated. The page provides stats, filters, and a version distribution chart to help you understand your deployment landscape at a glance.

To inspect a specific device's version history (past upgrades, downgrades), click on the device name to open its detail modal.

AppVersion vs BinaryVersion

ExeWatch tracks two distinct version identifiers for each device. They serve different purposes and do not necessarily change together:

AppVersion BinaryVersion
What is it The product version name — a commercial or human-readable label chosen by the developer The technical build number auto-detected from the executable's file metadata
How it's set Manually via Config.AppVersion or Config.Release Auto-detected from executable (FileVersion / ProductVersion)
Format Free-form: can be numeric ("2.5.0"), a codename ("oxygen"), or a mix ("2.0-oxygen") Always numeric: "X.Y.Z.W" (e.g. "10.0.0.0")
Changes with each build? Not necessarily. A product version name may stay the same across multiple builds (e.g. "v2.0-oxygen" can ship as binary 2.0.0.1, 2.0.0.2, etc.) Yes. Every recompile can produce a new build number
Example "2.0-oxygen", "neon", "3.1.0" "2.0.0.1234", "10.0.0.0"
Tip: Both values are always displayed together on the Updates page so you get the full picture. AppVersion tells you what product release the customer has; BinaryVersion tells you what exact build is running.
How "Latest Version" is Determined

The Latest Version stat card shows the highest version currently deployed across all devices. Since version identifiers can vary in format, ExeWatch uses a gradual fallback strategy to determine which version is "the latest":

Priority Field used How it compares When it applies
1st BinaryVersion Numeric comparison of each segment (e.g. 10.0.0.0 > 9.0.0.0) Always preferred when available, since it's auto-detected and always numeric
2nd AppVersion (numeric parts) Extracts numbers from the string (e.g. "v3.0-neon"3.0) and compares numerically When BinaryVersion is missing or identical across versions
3rd Last activity date The version used by the most recently active device wins Last resort: when neither BinaryVersion nor AppVersion contain usable numbers (e.g. pure codenames like "oxygen" vs "neon")
Examples
BinaryVersion differs

Device A: vOxygen (bin 1.0.0.0)

Device B: vNeon (bin 2.0.0.0)

Latest = vNeon (bin 2 > 1)

No binary, numbers in AppVersion

Device A: v2.0-oxygen (no binary)

Device B: v3.0-neon (no binary)

Latest = v3.0-neon (3.0 > 2.0)

Pure codenames, no numbers

Device A: oxygen (seen yesterday)

Device B: neon (seen today)

Latest = neon (more recent activity)

Recommendation: For the most reliable version tracking, make sure your build process sets a FileVersion in the executable metadata. This gives ExeWatch a numeric BinaryVersion to work with, ensuring accurate version ordering regardless of your AppVersion naming convention.
Adoption Percentage

The Adoption stat shows what percentage of your devices are running the latest version (as determined above). A low adoption percentage means many devices are still on older versions — you may want to encourage your customers to update.

Use the Version Distribution chart and the version filter to drill down into which specific versions are still in use and by which customers.


Usage Analytics

Understand how your software is being used. The Usage page provides session counts, device activity, customer rankings, usage patterns, and error rate trends — all computed automatically from your existing log data. No additional SDK configuration needed.

How it Works

Every time your SDK initializes, it generates a unique session ID and sends an "Application started" log. ExeWatch uses this data — along with device IDs, customer IDs, and timestamps — to compute usage analytics automatically.

Navigate to Usage in the app toolbar and select a time period (1d, 7d, 30d, 90d, or All) to explore your application's usage data.

Period Selector

All widgets on the Usage page respect the selected period. The time bucket granularity adapts automatically:

Period Time Range Bucket Size
1dLast 24 hours1 hour
7dLast 7 days1 day
30dLast 30 days1 day
90dLast 90 days1 week
AllAll available data1 month

Each stat card also shows a delta percentage comparing the current period to the previous period of equal length (e.g., last 7 days vs the 7 days before that).

Widgets
Summary Cards

Four stat cards at the top: Sessions (unique app runs), Devices (unique machines), Customers (unique end-users), and Avg Session (average time between first and last log of each session, excluding single-log sessions).

Sessions Over Time

Line chart showing sessions and devices per time bucket. Empty buckets (days/hours with no activity) are shown as zero, so you can see gaps clearly.

Usage Heatmap

A 7×24 grid showing session activity by day of week and hour of day (UTC). Brighter cells mean more sessions. Hover on any cell to see the exact count.

Top Customers

Table ranking your top 10 customers by session count, with device count and last active timestamp. Useful for identifying your most engaged users and spotting customers that have gone silent.

OS Distribution

Doughnut chart showing the top 10 OS versions across active devices. Full OS strings are displayed in a legend below the chart, with device counts and percentages.

Error Rate Trend

Line chart showing the error rate (percentage of ERROR + FATAL logs out of all logs) per time bucket. Use the version dropdown to filter by one or more app versions. Buckets with no traffic show as gaps (not 0%).

No SDK changes needed. Usage Analytics works automatically with any version of the ExeWatch SDK. All metrics are derived from the session IDs, device IDs, and customer IDs already sent with every log event.

Health Monitoring

Each application gets an automatic health status — Healthy, Degraded, or Critical — visible in the dashboard and sidebar. When the status changes you receive an email notification (and a Discord message, if configured).

What is Monitored

Three signals are evaluated over the last 24 hours. The overall status is the worst of the three.

Signal What it measures Degraded (default) Critical (default)
Error rate % of error + fatal events over total ≥ 1% ≥ 5%
Crashes Number of fatal events ≥ 1 ≥ 3
Slow operations Pro & Business % of timing measurements above the slow threshold ≥ 5% ≥ 20%

If there are fewer than 100 events in 24h, the error rate shows as Insufficient data instead of a potentially misleading percentage. All thresholds are customizable per application in App Settings → Health Thresholds.

The Slow operations signal is available on Pro and Business plans. On the free plan, health is based on error rate and crashes.
Stable Notifications

ExeWatch won't flood your inbox when a metric hovers near a threshold. Three safeguards keep notifications meaningful:

  • Hysteresis — once a signal enters a worse state, it needs to improve by a 30% margin before it can recover. For example, an error rate that crossed 1% (Degraded) must drop below 0.7% to return to Healthy.
  • Consecutive confirmation — a new status must hold for 3 consecutive checks before it is accepted. A single spike won't change the status.
  • Cooldown — after a notification is sent, the next one is held for at least 60 minutes (configurable in App Settings).
No SDK changes needed. Health monitoring works automatically from the data your SDK already sends. Tune thresholds and cooldown in App Settings.


Performance Timing

Measure how long operations take in your application. Identify slow queries, API calls, and bottlenecks.

How It Works
  1. Call StartTiming('operation_name') before the operation
  2. Call EndTiming('operation_name') after it completes
  3. View timing statistics in the ExeWatch dashboard (Timing tab)
  4. Operations exceeding the slow threshold are highlighted
Delphi Example
// Basic timing with try..except
EW.StartTiming('database_query');
try
  RunMyDatabaseQuery;
  EW.EndTiming('database_query');  // success
except
  on E: Exception do
  begin
    EW.EndTiming('database_query',
      TJSONObject.Create(TJSONPair.Create('error', E.Message)), False);
    raise;
  end;
end;

// With category for grouping
EW.StartTiming('load_customers', 'database');
try
  LoadCustomersFromDB;
  EW.EndTiming('load_customers');
except
  on E: Exception do
  begin
    EW.EndTiming('load_customers',
      TJSONObject.Create(TJSONPair.Create('error', E.Message)), False);
    raise;
  end;
end;
JavaScript Example
// Basic timing
ew.startTiming('api_call');
await fetch('/api/data');
ew.endTiming('api_call');

// With category
ew.startTiming('render_chart', 'ui');
renderComplexChart();
ew.endTiming('render_chart');
Tip: Set the slow threshold when creating your application. Operations exceeding this threshold are marked as "slow" in timing reports. Default is 1000ms.

Metrics (Counters & Gauges)

Track numeric values over time with counters and gauges. Monitor API call rates, memory usage, active connections, and more.

What are Metrics?

ExeWatch supports two types of metrics, inspired by Prometheus:

Counter

A value that only increases. Use counters to track how many times something happens.

Examples: API calls, cache hits, orders processed, errors encountered.

Gauge

A value that can go up or down. Use gauges to track the current state of something.

Examples: memory usage, active connections, queue depth, CPU temperature.

How It Works
  1. Your application records metrics using IncrementCounter or RecordGauge
  2. The SDK aggregates values locally (min, max, avg, count)
  3. Every 60 seconds, aggregated data is flushed to the ExeWatch server
  4. For periodic gauges, the SDK automatically samples registered callbacks at a configurable interval (default 30s)
  5. View metrics in the Metrics tab of your application dashboard
Quota: Metric data points count toward your monthly event quota (same as logs). Each flush interval produces one data point per unique metric name+tag combination.
Delphi Examples
Counters
// Increment by 1 (default)
EW.IncrementCounter('api_calls');

// Increment by a specific value
EW.IncrementCounter('bytes_processed', FileSize);

// With a tag for grouping
EW.IncrementCounter('api_calls', 1, 'network');
EW.IncrementCounter('cache_hits', 1, 'database');
Gauges (manual)
// Record a point-in-time value
EW.RecordGauge('active_connections', ActiveCount);
EW.RecordGauge('queue_depth', Queue.Count, 'jobs');
Gauges (periodic / automatic)
// Register a callback that the SDK samples every 30s automatically
EW.RegisterPeriodicGauge('memory_mb',
  function: Double
  begin
    Result := GetProcessMemoryMB;
  end,
  'system');

// Unregister when no longer needed
EW.UnregisterPeriodicGauge('memory_mb');
Configuration
// Set gauge sampling interval (default 30s, min 10s)
Config.GaugeSamplingIntervalSec := 15;
JavaScript Examples
// Counters
ew.incrementCounter('page_views');
ew.incrementCounter('api_calls', 1, 'network');

// Manual gauge
ew.recordGauge('items_in_cart', cart.length);

// Periodic gauge (auto-sampled every 30s)
ew.registerPeriodicGauge('dom_nodes', function() {
  return document.querySelectorAll('*').length;
}, 'performance');

// Unregister
ew.unregisterPeriodicGauge('dom_nodes');

// Force flush metrics immediately
ew.flushMetrics();
Configuration
// Set gauge sampling interval in config (default 30, min 10)
window.ewConfig = {
  apiKey: 'ew_web_xxx',
  customerId: 'customer123',
  gaugeSamplingIntervalSec: 15
};
Tip: Use tags to group related metrics (e.g., 'system', 'network', 'database'). Tags appear as filters in the Metrics dashboard, making it easy to focus on specific areas.

User Identity

Associate logs with specific users to understand who is affected by issues.

What is User Identity?

User Identity represents the person currently using your application. This is different from Customer (which represents the company/organization) and Device (which represents the computer).

Customer

The company or organization that purchased/uses your software. Example: "ACME Corporation"

Device

A specific computer or installation. Example: "john@WORKSTATION-01"

User

The individual person logged in. Example: "John Doe (john@acme.com)"

Example scenario: ACME Corporation (Customer) has 50 employees. Each employee has their own computer (Device). When John Doe logs into the app on his computer, you call SetUser so all his actions are tagged with his identity.

How to Use SetUser

Call SetUser after the user logs into your application. All subsequent log events will include this user information.

Delphi
// After user logs in successfully
EW.SetUser(
  'user-12345',     // ID - unique identifier
  'john@acme.com',  // Email (optional)
  'John Doe'        // Display name (optional)
);

// Now all logs include user info
EW.Info('Opened invoice #123', 'invoices');
EW.Warning('Slow query detected', 'db');
EW.Error('Failed to save', 'core');
// All these logs will show: user_id=user-12345

// When user logs out
EW.ClearUser;
EW.Info('User logged out', 'auth');
JavaScript
// After user logs in (pass an object)
ew.setUser({
  id: 'user-12345',
  email: 'john@acme.com',
  name: 'John Doe'
});

// All logs now tagged with user
ew.info('Added item to cart', 'cart');
ew.error('Payment failed', 'checkout');

// On logout
ew.clearUser();
What Values Should I Use?
Parameter Required What to use Examples
ID Required A unique, stable identifier for the user. Use your internal user ID from your database. "user-12345", "USR_abc123", "42"
Email Optional User's email address. Useful for contacting affected users. "john@acme.com"
Name Optional Display name for easier identification in the dashboard. "John Doe", "J. Smith"
Tip: The ID should be stable (doesn't change) and unique per user. Don't use values that change (like session tokens) or might be shared (like "admin").
Why Use User Identity?
  • Find affected users - When an error occurs, see who was affected
  • Reproduce issues - Follow a specific user's actions to understand what went wrong
  • Contact users - Reach out to users experiencing problems
  • Usage patterns - Understand how different users use your app
  • Support tickets - Link support requests to specific log trails
  • Impact analysis - Know how many unique users are affected by a bug
Privacy: Only collect user information you have consent to store. In many cases, just the user ID is enough to correlate issues without storing personal data.

Global Tags

Add key-value metadata that gets attached to all log events. Use tags for filtering and grouping.

Delphi
// Set tags
EW.SetTag('environment', 'production');
EW.SetTag('feature_flags', 'beta_ui');
EW.SetTag('subscription', 'enterprise');

// Remove a tag
EW.RemoveTag('feature_flags');

// Clear all tags
EW.ClearTags;
JavaScript
// Set tags
ew.setTag('environment', 'production');
ew.setTag('ab_test', 'variant_b');

// Remove a tag
ew.removeTag('ab_test');

// Clear all tags
ew.clearTags();
Common Tag Examples
Tag Values Use Case
environment production, staging, development Filter logs by environment
tenant customer-specific IDs Multi-tenant applications
feature checkout, reporting, import Track logs by feature area
subscription free, pro, enterprise Analyze by subscription tier

Hardware Info

The SDK automatically collects device hardware information to help you understand the environment where issues occur.

Collected Information (Delphi SDK)
System
  • OS type and version
  • Hostname and username
  • Timezone and locale
  • System language
  • System boot time
  • Local IP addresses
Hardware
  • CPU name and architecture
  • Physical and logical cores
  • Total and available memory
  • Disk drives (size, free space, type)
  • Monitors (resolution, color depth)
Application
  • Executable path
  • Working directory
  • Command line
  • File version (auto-detected)
  • Product name
  • Company name
Custom Device Info

Add your own key-value data to device info for application-specific context.

// Set custom info at initialization
Config := TExeWatchConfig.Create('ew_desk_xxx', 'ACME-Corp');
Config.InitialCustomDeviceInfo := [
  TPair.Create('license_type', 'enterprise'),
  TPair.Create('database', 'PostgreSQL 15')
];
InitializeExeWatch(Config);

// Or set/update custom info at runtime
EW.SetCustomDeviceInfo('active_modules', 'accounting,inventory');
EW.SendCustomDeviceInfo;  // Send accumulated custom info

// Or set and send in one call
EW.SendCustomDeviceInfo('last_sync', '2024-01-15T10:30:00');

Offline Mode & Persistence

The SDK is designed to never lose logs, even when the network is unavailable or the app crashes.

How It Works
  1. Buffer - Logs are collected in memory
  2. Persist - When buffer is full or interval elapses, logs are written to disk
  3. Ship - Background thread sends files to server
  4. Retry - Failed sends are automatically retried
  5. Cleanup - Successfully sent files are deleted
Benefits
  • Logs survive app crashes
  • Works offline (syncs when online)
  • Non-blocking (async background sending)
  • Automatic retries on failure
  • Oldest logs sent first (FIFO)
Storage location: By default, logs are stored in %LOCALAPPDATA%\ExeWatch\pending. You can customize this with Config.StoragePath.

WaitForSending

Flushes the in-memory buffer and blocks until every pending event has been shipped to the server (or the timeout elapses). Returns the number of events still pending — 0 means fully drained.

When to use it

The SDK ships events asynchronously in a background thread. For long-running applications (desktop GUI, Windows services) this is transparent — the thread keeps running and Shutdown handles the final flush automatically.

For short-lived processes — console tools, batch jobs, test suites, CI scripts — the process may exit before the shipper thread has had time to send everything. Calling WaitForSending before Shutdown ensures no events are lost.

Use WaitForSending
  • Console applications and CLI tools
  • Batch processors and import jobs
  • Test suites and CI pipelines
  • Short-lived scripts (Python, etc.)
  • Any process that exits within seconds of its last log
Not needed
  • Desktop GUI applications (long-running)
  • Windows services (long-running)
  • Web servers and background daemons
  • Any process that stays alive for minutes or more
If the timeout elapses with events still pending, they are not lost — they remain on disk and will be retried the next time the application runs. This is the Offline Mode queue at work.
Usage by SDK
program MyConsoleApp;

{$APPTYPE CONSOLE}

uses
  SysUtils, ExeWatchSDKv1;

var
  Remaining: Integer;
begin
  InitializeExeWatch('ew_win_your_api_key', 'ACME-Corp');

  EW.Info('Batch job started', 'job');
  RunBatchJob;
  EW.Info('Batch job completed', 'job');

  // Block until all events are shipped (timeout: 15 seconds)
  Remaining := EW.WaitForSending(15);
  if Remaining > 0 then
    Writeln('Warning: ' + IntToStr(Remaining) + ' event(s) still queued — will retry on next run');

  // Shutdown after WaitForSending — order matters
  FinalizeExeWatch;
end.
uses
  SysUtils, ExeWatchSDKv1Imports;

var
  Remaining: Integer;
begin
  EWInitialize('ew_win_your_api_key', 'ACME-Corp');

  EWInfo('Batch job started', 'job');
  RunBatchJob;
  EWInfo('Batch job completed', 'job');

  // Block until all events are shipped (timeout: 15 seconds)
  Remaining := ew_WaitForSending(15);
  if Remaining > 0 then
    Writeln('Warning: ' + IntToStr(Remaining) + ' event(s) still queued');

  ew_Shutdown;
end.
// Console / batch application example
#define EW_DYNAMIC_LOAD
#include "ExeWatchSDKv1.h"

int main()
{
    ew_LoadSDK();
    ew_Initialize(L"ew_win_your_api_key", L"ACME-Corp", L"");

    ew_Info(L"Batch job started", L"job");
    RunBatchJob();
    ew_Info(L"Batch job completed", L"job");

    // Block until all events are shipped (timeout: 15 seconds)
    int remaining = ew_WaitForSending(15);
    if (remaining > 0)
        fwprintf(stderr, L"%d event(s) still queued — will retry on next run\n", remaining);

    ew_Shutdown();
    ew_UnloadSDK();
    return 0;
}
using ExeWatch;

ExeWatchSdk.Initialize(new ExeWatchConfig { ApiKey = "ew_win_your_api_key", CustomerId = "ACME-Corp" });

EW.Info("Batch job started", "job");
RunBatchJob();
EW.Info("Batch job completed", "job");

// Block until all events are shipped (timeout: 15 seconds)
int remaining = EW.WaitForSending(15);
if (remaining > 0)
    Console.Error.WriteLine($"{remaining} event(s) still queued — will retry on next run");

ExeWatchSdk.Shutdown();
from exewatch import initialize_exewatch, ew, finalize_exewatch

initialize_exewatch('ew_win_your_api_key', 'ACME-Corp')

ew.info('Batch job started', 'job')
run_batch_job()
ew.info('Batch job completed', 'job')

# Block until all events are shipped (timeout: 15 seconds)
remaining = ew.wait_for_sending(15)
if remaining > 0:
    print(f"{remaining} event(s) still queued — will retry on next run", file=sys.stderr)

finalize_exewatch()
Return value & timeout guidance
Return value Meaning
0 All events shipped successfully before the timeout
> 0 Timeout elapsed — that many events are still on disk and will be retried on next run

A timeout of 10–30 seconds is appropriate for most batch jobs on a normal connection. For very large event volumes or slow connections, increase accordingly. Always call WaitForSending before Shutdown / FinalizeExeWatch.


Sampling

Reduce event volume by sampling a percentage of logs. Useful for high-traffic applications.

Configuration
// Send only 10% of debug/info/warning logs
Config := TExeWatchConfig.Create('ew_desk_xxx', 'ACME-Corp');
Config.SampleRate := 0.1;  // 10%
InitializeExeWatch(Config);

// These have a 10% chance of being sent
EW.Debug('Trace message');
EW.Info('User action');

// These are ALWAYS sent (100%)
EW.Error('Something failed');
EW.Fatal('Critical error');
Sample Rate Values
Value % Sent Use Case
1.0 100% Default - send everything
0.5 50% Moderate reduction
0.1 10% High-volume apps
0.01 1% Very high volume
Important: Error and Fatal level events always bypass sampling and are sent 100% of the time. This ensures you never miss critical issues.

Stack Traces

ExeWatch automatically captures and displays stack traces for all Error and Fatal level logs. The stack trace shows the exact function call chain that led to the error, making it easy to pinpoint the root cause.

What You Get

In the Log Detail modal, Error and Fatal logs show a Stack Trace section with the full call chain:

MainFormU.TMainForm.btnProcessClick (line 87)
MainFormU.TMainForm.ValidateInput (line 123)
DataModuleU.TDataModule.SaveRecord (line 456)
Vcl.Controls.TControl.Click (line 7459)
Compiler Settings

Stack traces require specific compiler settings. The table below shows what to enable for each platform:

Setting Where Win32 Win64
Map file = Detailed Project Options > Linking Required Required
Stack frames = True Project Options > Compiling Required Not needed
Debug information = True Project Options > Compiling Required Not needed
Win64 is simpler: only the map file is needed. Win64 uses table-based stack unwinding which doesn't depend on stack frames or debug info.

The .map file must be deployed alongside the .exe. Without it, stack traces show raw memory addresses instead of function names (still useful, can be resolved later with the map file).

What Gets Captured

Stack traces are automatically attached in these scenarios:

Scenario How Win32 Win64
EW.Error('...') / EW.Fatal('...') Direct call from your code Full stack Full stack
EW.ErrorWithException(E) in except Called from a try/except block Full stack Full stack
Unhandled exception (console app) Automatic via System.ExceptProc Full stack Full stack
VCL/FMX GUI exception (unhandled) Automatic via Application.OnException VCL frames only Full stack
Win32 VCL/FMX tip: for best results with GUI exceptions, wrap your critical code in try/except and call EW.ErrorWithException(E). This captures the full stack trace. Unhandled GUI exceptions on Win32 only show VCL message loop frames due to SEH stack unwinding limitations.
Example: Exception with Stack Trace
try
  // your code that might fail
  ProcessOrder(OrderId);
except
  on E: Exception do
  begin
    EW.ErrorWithException(E, 'orders', 'Failed to process order');
    // optional: re-raise if you want the default error dialog
    raise;
  end;
end;
Coexisting with madExcept / EurekaLog

Tools like madExcept and EurekaLog install low-level exception hooks that intercept exceptions before the VCL's Application.OnException. When that happens, ExeWatch never sees the exception unless you forward it. Better still, madExcept has already resolved the stack with unit names and line numbers — passing that string to ExeWatch gives you a symbolicated, searchable stack in the dashboard without shipping map files alongside your exe.

Inside the madExcept callback, build an extra_data JSON with a stack_trace field set to madExcept's BugReport, then pass it to EW.Log(llError, ...). The SDK uses the provided stack as-is and does not replace it.

// madExcept bridge — forwards every intercepted exception to ExeWatch
// with madExcept's fully-resolved stack trace. Drop this unit into your
// project; the initialization section installs the callback automatically.
unit ExeWatchMadExceptBridgeU;

interface

implementation

uses System.SysUtils, System.JSON, madExcept, ExeWatchSDKv1;

procedure ExeWatchMadExceptHandler(const ExceptIntf: IMEException;
  var Handled: Boolean);
var
  ExtraData: TJSONObject;
  ExClass, ExMsg: string;
  E: TObject;
begin
  if (ExceptIntf = nil) or (not ExeWatchIsInitialized) then
    Exit;

  // Prefer the original Exception's class/message; fall back to madExcept's
  // fields for hardware faults that arrived before the Delphi wrapper.
  E := ExceptIntf.ExceptObject;
  if Assigned(E) and (E is Exception) then
  begin
    ExClass := E.ClassName;
    ExMsg   := Exception(E).Message;
  end
  else
  begin
    ExClass := ExceptIntf.ExceptClass;
    ExMsg   := ExceptIntf.ExceptMessage;
  end;

  // Pre-populate extra_data; the SDK honors stack_trace when already set
  // and will NOT overwrite it with its own StackWalk-based capture.
  ExtraData := TJSONObject.Create;
  ExtraData.AddPair('exception_class',   ExClass);
  ExtraData.AddPair('exception_message', ExMsg);
  ExtraData.AddPair('stack_trace',       ExceptIntf.BugReport);

  EW.Log(llError, ExMsg, 'exception', ExtraData);

  // Handled deliberately left untouched: madExcept's own dialog,
  // bug-report save, and email flow continue to work.
end;

initialization
  // stDontSync: callback may fire on any thread; EW.* is thread-safe.
  RegisterExceptionHandler(ExeWatchMadExceptHandler, stDontSync);
finalization
  UnregisterExceptionHandler(ExeWatchMadExceptHandler);
end.

A complete, compile-tested sample project (Delphi 12 + 13, Win32 + Win64, madExcept 5.2.0) is available at exewatchsamples » SpecificScenarios/madExceptIntegration — it includes the bridge unit above, a demo form, and a README with variations (slimmer stack-only payload, filtering non-crash exceptions, etc.).

EurekaLog exposes an equivalent callback (RegisterEventExceptionNotify). The principle is identical: extract EurekaLog's resolved stack and pass it to EW.ErrorWithException(E, StackTrace, ...).

Graceful Degradation

If the required settings are missing, the log will include a no_stacktrace_reason code:

CodeMeaningFix
1RtlCaptureStackBackTrace not availableRequires Windows XP or later
2Map file not foundSet Map file = Detailed in Linking options, deploy .map with .exe
30 frames capturedEnable Stack frames + Debug information in Compiling options (Win32)
4Platform not supportedStack traces are currently Windows-only
Platform Support
PlatformStatusNotes
Windows Win64Full supportAll scenarios work, only map file needed
Windows Win32SupportedRequires Stack frames + Debug info. Use try/except for GUI exceptions
LinuxPlanned

Webhooks

ExeWatch can send alert notifications to external services via webhooks. Two types are available:

IntegrationPlanDescription
Discord Pro+ Sends rich embeds to a Discord channel
Generic Webhook Business Sends JSON POST to any HTTPS endpoint
Setup

Go to your app's Alerts page and click the Integrations button. Configure the webhook URL and save. ExeWatch will send notifications when alerts fire (log level, timing, or health status changes).

Generic Webhook Payload

Each webhook delivery is a POST request with a JSON body:

{
  "event": "alert.fired",
  "alert_type": "log_level",
  "alert_name": "Error rate alert",
  "app_name": "My Application",
  "condition": "errors >= 3 in 10 min",
  "message": "5 error events in the last 10 minutes",
  "app_id": "937b6080-0088-4bde-942d-91552a640e1a",
  "timestamp": "2026-03-21T14:30:00.000Z",
  "url": "https://exewatch.com/ui/apps/937b6080-.../logs"
}
Alert Types
alert_typeTrigger
log_levelError/Fatal log count exceeds threshold
timingSlow operations exceed threshold
health_criticalApp health changed to Critical (red)
health_degradedApp health changed to Degraded (yellow)
health_recoveryApp health returned to Healthy (green)
Authentication

Each request includes an X-ExeWatch-Token header with a secret token generated when you configure the webhook. Verify this token on your server to confirm the request is from ExeWatch:

# Python example
@app.post("/webhook")
def handle_webhook(request):
    token = request.headers.get("X-ExeWatch-Token")
    if token != "whsec_your_secret_here":
        return Response(status_code=401)

    data = request.json()
    print(f"Alert: {data['alert_name']} - {data['message']}")
    return Response(status_code=200)
Headers
HeaderValue
Content-Typeapplication/json
User-AgentExeWatch-Webhook/1.0
X-ExeWatch-TokenYour webhook secret
X-ExeWatch-Eventalert.fired
Retry Policy

If your endpoint returns a non-2xx status code or is unreachable, ExeWatch retries the delivery up to 3 times with increasing delays (30 seconds, 2 minutes, 5 minutes). After 3 failed attempts, the delivery is marked as failed. Delivery status is tracked internally for troubleshooting.

Testing

Use the Send Test button in the Integrations modal to send a simulated alert to your endpoint. You can choose the alert type to test. The test payload uses the same format as production alerts, with [TEST] prefixed in the message field for easy identification.

TLS / Self-Signed Certificates

ExeWatch accepts self-signed certificates on webhook endpoints. This is useful for development and internal servers.


Log Levels

ExeWatch supports 5 severity levels to help you categorize and filter your logs.

Level Method When to Use
Debug EW.Debug() / ew.debug() Detailed information for debugging. Usually disabled in production.
Info EW.Info() / ew.info() General information about app execution. User actions, state changes.
Warning EW.Warning() / ew.warning() Something unexpected but not critical. Deprecation notices, slow operations.
Error EW.Error() / ew.error() Errors that affect functionality but don't crash the app. Failed API calls, validation errors.
Fatal EW.Fatal() / ew.fatal() Critical errors that crash or severely impact the app. Unhandled exceptions.

Best Practices

Do
  • Use meaningful tags to categorize logs (e.g., "DB", "API", "UI")
  • Set user identity when available for easier debugging
  • Log important user actions as Info level
  • Include context in error messages
  • Use breadcrumbs to track user flow
Don't
  • Log sensitive data (passwords, credit cards, PII)
  • Use Debug level for everything in production
  • Log in tight loops (can generate excessive events)
  • Ignore the tag parameter - it helps filtering
  • Use Fatal for non-critical errors

Privacy & Terms of Service Guide

When you integrate ExeWatch into your application, you collect operational data from your end users. Depending on your jurisdiction, you may need to disclose this in your Terms of Service or Privacy Policy. Below you'll find exactly what data the SDK collects and a ready-to-use template you can adapt.

What Data Does the SDK Collect?
Delphi Native SDK — Desktop, Server, Mobile

Collected automatically:

  • Device IDusername@hostname (e.g., jack@MYWORKLAPTOP), where username is the logged-in OS user and hostname is the machine name. If your environment uses personal names as OS usernames (e.g., Active Directory), you can optionally set AnonymizeDeviceId := True to replace the username with a non-reversible hash (e.g., a3f2b1c4@MYWORKLAPTOP). This is not required — it is an option available if your privacy policy demands it
  • System info — OS version, timezone, locale, language
  • Hardware — CPU, RAM, disk space, screen resolution
  • Application — binary version, optional app version, executable path
  • Session ID — random identifier per app run (not tied to user identity)
  • Log events — level, message, tag, thread, timestamps
  • Performance timing — operation names and durations (if used)

Collected only if you set it:

  • Customer ID — identifier you assign (e.g., company name, license key)
  • User identity — only if you call SetUser() with ID, email, or name
  • Breadcrumbs — user actions trail, only if you call AddBreadcrumb()
  • Global tags — key-value pairs you explicitly set with SetTag()
  • Custom log messages — only what your code explicitly logs
JavaScript SDK — Browser

Collected automatically:

  • Device ID — random 8-character string stored in localStorage (e.g., a3f8b2c1). No OS username or hostname is collected.
  • Browser info — user agent, screen size, language
  • Session ID — random identifier per page load
  • Log events — level, message, tag, timestamps
  • Performance timing — operation names and durations (if used)
  • Automatic breadcrumbs — navigation (pushState/replaceState), user clicks (element info), HTTP requests (fetch/XHR method, URL, status). Collected by default when auto-capture is enabled.

Collected only if you set it:

  • Customer ID — identifier you assign
  • User identity — only if you call setUser()
  • Custom breadcrumbs — additional context you add via addBreadcrumb()
  • Global tags — key-value pairs you explicitly set
  • Custom log messages — only what your code explicitly logs
Important: The SDK does not collect keystrokes, screen content, file contents, browsing history, GPS location, or any data beyond what is listed above. You have full control over what personal information is sent.
Where Is Data Stored?
  • Servers: ExeWatch data is stored on servers located in the European Union (EU)
  • Encryption: All data is transmitted over HTTPS (TLS 1.2+)
  • Retention: Data is retained according to your ExeWatch plan (7, 30, or 90 days) and automatically deleted after that period
  • Sub-processors: See our Data Processing Agreement (DPA) for the full list of sub-processors
Privacy Policy / ToS Template

Copy and adapt the text below for your application's Privacy Policy or Terms of Service. Replace the placeholders in [brackets] with your own information.

Disclaimer: This template is provided as a starting point only and does not constitute legal advice. ExeWatch (bit Time Professionals S.r.l.) acts exclusively as a data processor and is not responsible for the data that you, as the data controller, choose to collect, transmit, or store through the SDK. It is your sole responsibility to ensure that your use of ExeWatch complies with all applicable data protection laws in your jurisdiction. We recommend consulting a qualified legal professional to review your Privacy Policy before publication.
Application Monitoring and Data Collection

Data controller

[Your Company Name]
[Your registered address]
Email: [your contact email]
[If applicable:] Data Protection Officer: [DPO name and contact]

Overview

[Your Application Name] uses ExeWatch, a third-party application monitoring service provided by bit Time Professionals S.r.l. (Italy), to monitor application performance, detect errors, and improve software quality.

Legal basis for processing

[Choose the legal basis that applies to your situation:]

Option A — Legitimate interest (Art. 6(1)(f) GDPR): The processing of technical diagnostic data is necessary for our legitimate interest in ensuring the reliability, security, and quality of our software. We have conducted a balancing test and determined that this interest is not overridden by your rights and freedoms, as the data collected is limited to technical information and does not include personal content.

Option B — Contractual necessity (Art. 6(1)(b) GDPR): The processing of diagnostic data is necessary for the performance of the contract between you and [Your Company Name], specifically to provide software support, maintain product quality, and fulfill our obligation to deliver a reliable application.

Option C — Consent (Art. 6(1)(a) GDPR): Diagnostic data is collected only with your explicit consent, which you can withdraw at any time. See the "Opting out" section below.

What data is collected

When you use [Your Application Name], the following technical data is automatically collected and sent to ExeWatch servers:

Device information: computer name (hostname), operating system username (the currently logged-in user, e.g. "jack"), operating system version, CPU model, RAM amount, available disk space, screen resolution. On desktop, server, and mobile platforms, each device is identified as username@hostname (e.g., "jack@MYWORKLAPTOP").
Application data: application version, session identifier (random, not tied to your identity), timestamps of events
Diagnostic data: error messages, performance measurements, and operational log entries generated by the application

[If you use SetUser() or Customer ID, add the following:]

Additionally, the following information may be associated with the collected data:
Account identifier: your license key or company name, used to associate diagnostic data with your organization
User identifier: [describe what you pass — e.g., username, email, or internal ID]

What data is NOT collected

The monitoring system does not collect: keystrokes, screen content or screenshots, file contents, browsing history, GPS location, passwords, or any personal documents.

Purpose of data collection

This data is collected solely for the following purposes:
• Detecting and diagnosing software errors and crashes
• Monitoring application performance and reliability
• Understanding software usage patterns to improve the product
• Tracking software version adoption across installations

Data storage and retention

Collected data is transmitted over encrypted connections (HTTPS/TLS 1.2+) to ExeWatch servers located in the European Union. Data is retained for a maximum of [7/30/90] days, after which it is automatically and permanently deleted.

Third-party data processor

ExeWatch (bit Time Professionals S.r.l., Italy) acts as a data processor on behalf of [Your Company Name] (data controller). A Data Processing Agreement (DPA) governs the relationship and ensures GDPR compliance. For details, see: https://exewatch.com/ui/dpa

Your rights

Under the General Data Protection Regulation (GDPR) and other applicable data protection laws, you have the following rights regarding your personal data:

Right of access (Art. 15) — request a copy of the data we hold about you
Right to rectification (Art. 16) — request correction of inaccurate data
Right to erasure (Art. 17) — request deletion of your data
Right to restriction (Art. 18) — request that we limit how your data is processed
Right to data portability (Art. 20) — request your data in a machine-readable format
Right to object (Art. 21) — object to processing based on legitimate interest

To exercise any of these rights, contact: [your contact email]

You also have the right to lodge a complaint with your local data protection supervisory authority if you believe your data is being processed unlawfully.

Opting out

[Choose the option that matches your chosen legal basis:]

Option A (for Legitimate interest or Contractual necessity): You may object to the processing of diagnostic data by contacting us at [your contact email]. We will evaluate your request and cease processing unless we can demonstrate compelling legitimate grounds. The monitoring cannot be disabled individually within the application, but all data is handled in strict accordance with this policy and applicable data protection laws.

Option B (for Consent): You may withdraw your consent and disable diagnostic data collection at any time from [Settings > Privacy] in the application, or by contacting us at [your contact email]. Withdrawal of consent does not affect the lawfulness of processing carried out before the withdrawal.
Tips for Compliance
  • Avoid logging personal data: Don't pass usernames, emails, or personal info in log messages unless strictly necessary
  • Be transparent: If you use SetUser(), mention it in your Privacy Policy
  • Minimize data: Only log what you need for debugging. Use sampling to reduce volume
  • Consider opt-out: If required by your jurisdiction, provide users with a way to disable monitoring
  • Sign the DPA: If you process EU personal data, review and sign our Data Processing Agreement
  • Update regularly: Review your Privacy Policy when you add new SDK features (e.g., User Identity, Breadcrumbs)

Tutorials & Resources

Official SDK Samples

Ready-to-run demo projects for Delphi, JavaScript, and more. Clone, configure your API key, and start exploring.

View on GitHub
Video Tutorial

Getting started with ExeWatch — Delphi SDK integration walkthrough.

Watch on YouTube
Updates & Usage Analytics

Blog post covering the Updates page, Usage Analytics, and how to track your application adoption.

Read Blog Post

Ready to Get Started?

Create your free account and start monitoring in minutes.

Create Free Account

For bug reports, feedback, or questions: exewatch@bittime.it