Skip to content

Core SDK ​

The logstyx-js-core package is the foundation for all JS platform-specific Logstyx SDKs. It provides the core logging functionality and handles payload construction, sanitization, and authentication.

When to Use Core SDK ​

Use the core SDK when:

  • Building a custom integration for an unsupported framework
  • Creating your own wrapper with additional functionality
  • You need maximum control over the logging behavior
  • Platform-specific SDKs don't fit your use case

For Most Users

If you're using Node.js, React, Express, or other supported platforms, use the platform-specific SDK instead. They provide better developer experience with framework-specific features.

Installation ​

bash
npm install github:devatlogstyx/logstyx-js-core#release

Basic Usage ​

javascript
const { useLogstyx } = require('logstyx-js-core');

const logger = useLogstyx({
  projectId: 'your_project_id',
  apiKey: 'your_api_key',
  signatureFunc: (projectId, apiKey, payload) => {
    // Your signature implementation
    return { timestamp, signature };
  },
  sendFunc: (endpoint, options) => {
    // Your HTTP client implementation
    return fetch(endpoint, options);
  }
});

// Send logs
logger.info({ message: 'Application started' });
logger.error({ message: 'Error occurred', error: err });

Configuration Options ​

Required Options ​

projectId (string) ​

Your Logstyx project identifier. Get this from your project settings in the Logstyx dashboard.

javascript
projectId: 'proj_abc123xyz'

endpoint (string) ​

The API endpoint to send logs to. Defaults to the public Logstyx instance.

javascript
endpoint: 'https://your-domain.com/api/v1/logs'

For self-hosted instances, point this to your deployment.

Optional Options ​

apiKey (string) ​

Required for server-side authentication. Used to generate HMAC signatures.

javascript
apiKey: 'your_api_key_here'

Never Expose API Keys

API keys should only be used server-side. Never include them in client-side code or commit them to version control.

appid (string) ​

Required for React Native applications. Typically your app's bundle identifier in reverse domain notation.

javascript
appid: 'com.yourcompany.appname'

device (any) ​

Optional device identifier. Useful for tracking logs from specific devices or instances.

javascript
device: 'server-01' // or device UUID for mobile apps

signatureFunc (function) ​

Function to generate authentication signatures for server-side requests. Required when using apiKey.

Signature:

typescript
(projectId: string, apiKey: string, payload: object) => {
  timestamp: number,
  signature: string
}

Required implementation:

javascript
const crypto = require('crypto');

function signatureFunc(projectId, apiKey, payload) {
  const timestamp = new Date().getTime();

    let params = JSON.stringify(body);

    const Hash = projectId + params + parseInt(timestamp);
    const signature = crypto
        .createHmac(`SHA256`, apiKey)
        .update(Hash)
        .digest("hex")
        .toUpperCase();

    return {
        timestamp,
        signature
    }
}

Important

The signature must be generated exactly as shown above to match the server's validation:

  1. Message format: projectId + JSON.stringify(payload) + timestamp
  2. Timestamp must be in milliseconds
  3. Signature must be uppercase
  4. HMAC algorithm: SHA256

Any deviation will result in authentication failures.

sendFunc (function) ​

Custom function to send HTTP requests. Allows you to use your preferred HTTP client.

Signature:

typescript
(endpoint: string, options: object) => Promise<Response>

Default behavior:

javascript
function sendFunc(endpoint, options) {
  return fetch(endpoint, options);
}

Custom example with axios:

javascript
const axios = require('axios');

function sendFunc(endpoint, options) {
  return axios.post(endpoint, JSON.parse(options.body), {
    headers: options.headers
  });
}

API Methods ​

logger.info(data) ​

Send an informational log.

javascript
logger.info({ 
  message: 'User logged in',
  userId: '12345',
  timestamp: Date.now()
});

logger.warning(data) ​

Send a warning log.

javascript
logger.warning({ 
  message: 'API rate limit approaching',
  currentRate: 950,
  limit: 1000
});

logger.error(data) ​

Send an error log.

javascript
logger.error({ 
  message: 'Database connection failed',
  error: err.message,
  host: 'db.example.com'
});

logger.critical(data) ​

Send a critical log for severe issues.

javascript
logger.critical({ 
  message: 'System failure',
  service: 'payment-processor',
  impact: 'All transactions blocked'
});

logger.send(level, data) ​

Send a log with a custom level.

javascript
logger.send('info', { message: 'Custom log' });
logger.send('error', { message: 'Error log' });

logger.setContext(context) ​

Set persistent context that will be included in all subsequent logs.

javascript
logger.setContext({
  userId: '12345',
  sessionId: 'sess_xyz',
  environment: 'production'
});

// All logs after this will include the context
logger.info({ message: 'Action performed' });
// Sent as: { message: 'Action performed', userId: '12345', sessionId: 'sess_xyz', ... }

logger.clearContext(keys?) ​

Clear specific context keys or all context.

javascript
// Clear specific keys
logger.clearContext(['sessionId']);

// Clear all context
logger.clearContext();

Payload Structure ​

Generic Payload Format ​

The core SDK constructs payloads in the following format before sending:

javascript
{
  level: 'info',           // Log level
  projectId: 'proj_xyz',   // Your project ID
  appid: 'com.app.name',   // Optional: for React Native
  device: 'device-id',     // Optional: device identifier
  context: {               // Sanitized context
    userId: '123',
    environment: 'prod'
  },
  data: {                  // Sanitized and normalized data
    message: 'Log message',
    customField: 'value'
  }
}

Payload Sanitization ​

All payloads go through sanitization to remove invalid values:

Removed values:

  • undefined
  • null
  • Empty strings ("")
  • Empty arrays ([])
  • Empty objects ({})

Example:

javascript
// Input
logger.info({
  message: 'Test',
  validField: 'value',
  nullField: null,
  undefinedField: undefined,
  emptyString: '',
  emptyArray: [],
  emptyObject: {}
});

// Actual payload sent
{
  message: 'Test',
  validField: 'value'
  // All empty/null/undefined fields removed
}

Data Normalization ​

The data field is normalized with the following rules:

Error Objects ​

Errors are automatically expanded to have title, message and stack

javascript
const error = new Error('Something failed');
error.statusCode = 500;

logger.error(error);

// Normalized to:
{
  title: 'Error',
  message: 'Something failed',
  stack: '...',
  statusCode: 500
}

Depth Limiting ​

Objects are cloned up to a maximum depth of 2 levels:

javascript
logger.info({
  level1: {
    level2: {
      level3: {
        level4: 'value'
      }
    }
  }
});

// Normalized to:
{
  level1: {
    level2: '[Max depth reached]'
  }
}

Circular Reference Protection ​

Circular references are detected and replaced:

javascript
const obj = { name: 'test' };
obj.self = obj; // Circular reference

logger.info(obj);

// Normalized to:
{
  name: 'test',
  self: '[Circular]'
}

String Truncation ​

Long strings are truncated to 500 characters:

javascript
logger.info({ 
  longString: 'x'.repeat(1000) 
});

// Normalized to:
{
  longString: 'xxx...xxx (truncated)'
}

Array Limiting ​

Arrays are limited to the first 10 elements:

javascript
logger.info({ 
  items: Array(100).fill('item') 
});

// Normalized to:
{
  items: ['item', 'item', ...] // Only first 10 items
}

Private Properties ​

Properties starting with underscore are excluded:

javascript
logger.info({
  publicField: 'visible',
  _privateField: 'hidden'
});

// Normalized to:
{
  publicField: 'visible'
  // _privateField is excluded
}

Primitive Values ​

Primitive values are wrapped in a message object:

javascript
logger.info('Simple string');
// Becomes: { message: 'Simple string' }

logger.info(123);
// Becomes: { message: '123' }

logger.info(['array', 'items']);
// Becomes: { message: '["array","items"]' }

Authentication Flow ​

Server-Side (HMAC Signature) ​

When signatureFunc is provided:

  1. Payload is constructed and sanitized
  2. signatureFunc is called with projectId, apiKey, and payload
  3. Signature and timestamp are added to request headers
  4. Request is sent with authentication headers
javascript
// Headers sent
{
  "Content-Type": "application/json",
  "signature": "abc123...",
  "timestamp": 1234567890
}

Client-Side (Origin-Based) ​

For browser/React (no signatureFunc):

  1. Payload is constructed and sanitized
  2. Request is sent without signature headers
  3. Server validates based on allowed origins
javascript
// Headers sent
{
  "Content-Type": "application/json"
}

React Native (App ID) ​

For mobile apps with appid:

  1. Payload includes appid field
  2. Request is sent without signature headers
  3. Server validates based on registered app IDs

Advanced Usage ​

Custom HTTP Client ​

Use your own HTTP client for better control:

javascript
const axios = require('axios');

const logger = useLogstyx({
  projectId: 'proj_xyz',
  sendFunc: async (endpoint, options) => {
    try {
      const response = await axios({
        url: endpoint,
        method: options.method,
        headers: options.headers,
        data: JSON.parse(options.body),
        timeout: 5000
      });
      return response;
    } catch (error) {
      console.error('Failed to send log:', error);
      throw error;
    }
  }
});

Batch Logging ​

Implement batching by wrapping the core SDK:

javascript
class BatchLogger {
  constructor(logger, batchSize = 10, flushInterval = 5000) {
    this.logger = logger;
    this.batch = [];
    this.batchSize = batchSize;
    
    setInterval(() => this.flush(), flushInterval);
  }
  
  log(level, data) {
    this.batch.push({ level, data });
    if (this.batch.length >= this.batchSize) {
      this.flush();
    }
  }
  
  flush() {
    if (this.batch.length === 0) return;
    
    this.batch.forEach(({ level, data }) => {
      this.logger.send(level, data);
    });
    
    this.batch = [];
  }
}

const logger = useLogstyx({ projectId: 'proj_xyz' });
const batchLogger = new BatchLogger(logger);

Middleware Pattern ​

Add preprocessing to logs:

javascript
function createLoggerWithMiddleware(config, middleware) {
  const baseLogger = useLogstyx(config);
  
  return {
    info: (data) => baseLogger.info(middleware(data)),
    warning: (data) => baseLogger.warning(middleware(data)),
    error: (data) => baseLogger.error(middleware(data)),
    critical: (data) => baseLogger.critical(middleware(data)),
    setContext: baseLogger.setContext,
    clearContext: baseLogger.clearContext
  };
}

// Usage
const logger = createLoggerWithMiddleware(
  { projectId: 'proj_xyz' },
  (data) => ({
    ...data,
    timestamp: Date.now(),
    hostname: require('os').hostname()
  })
);

Best Practices ​

Use Platform-Specific SDKs ​

For most use cases, use platform-specific SDKs instead of core:

javascript
// ❌ Not recommended for Node.js
const { useLogstyx } = require('logstyx-js-core');

// ✅ Better for Node.js
const useLogstyx = require('logstyx-js-node');

Structure Your Data ​

Use structured data instead of plain strings:

javascript
// ❌ Avoid
logger.info('User 12345 logged in from 192.168.1.1');

// ✅ Better
logger.info({
  message: 'User logged in',
  userId: '12345',
  ipAddress: '192.168.1.1'
});

Leverage Context ​

Set context once for repeated values:

javascript
// ❌ Repetitive
logger.info({ message: 'Action 1', userId: '123', env: 'prod' });
logger.info({ message: 'Action 2', userId: '123', env: 'prod' });

// ✅ Better
logger.setContext({ userId: '123', env: 'prod' });
logger.info({ message: 'Action 1' });
logger.info({ message: 'Action 2' });

Handle Sensitive Data ​

Never log sensitive information:

javascript
// ❌ Dangerous
logger.info({ password: user.password, apiKey: config.apiKey });

// ✅ Safe
logger.info({ 
  action: 'user_login',
  userId: user.id,
  // Omit sensitive fields
});

Released under the MIT License.