PHPackages                             mhrshuvo/journey-log - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Logging &amp; Monitoring](/categories/logging)
4. /
5. mhrshuvo/journey-log

ActiveLibrary[Logging &amp; Monitoring](/categories/logging)

mhrshuvo/journey-log
====================

JSON based folder-wise user journey logger for Laravel applications. Track user behavior with organized JSON logs and built-in analytics dashboard.

1.0.1(2mo ago)23MITPHPPHP ^8.1CI passing

Since Mar 2Pushed 2mo agoCompare

[ Source](https://github.com/mhrshuvo/journey-log)[ Packagist](https://packagist.org/packages/mhrshuvo/journey-log)[ Docs](https://github.com/mhrshuvo/journey-log)[ RSS](/packages/mhrshuvo-journey-log/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (8)Versions (3)Used By (0)

JourneyLog Laravel Package
==========================

[](#journeylog-laravel-package)

JourneyLog is the "Black Box" for your Laravel application. Designed specifically for high-stakes industries like Flight, Bus, and Hotel Reservations or Dropshipping, it creates a secure, organized, and session-isolated audit trail of every user interaction and 3rd-party API response.

**Stop digging through massive, messy log files. Start seeing the full story of every booking.**

🌟 Why JourneyLog?
-----------------

[](#-why-journeylog)

In reservation systems, 3rd-party APIs (GDS, Wholesalers, Suppliers) change prices and availability in milliseconds. When a customer claims a price mismatch or a booking fails, you need evidence.

- **Session Isolation**: Every visitor gets their own unique JSON log file. No mixed data.
- **Folder-Wise Logic**: Automatically group logs into categories like `/search`, `/api-responses`, or `/bookings`.
- **Hybrid Ready**: Works seamlessly with Web (Sessions) and Stateless APIs (Headers).
- **Security First**: Built-in Global Masking recursively hides sensitive PII (passwords, API keys, tokens).

Installation
------------

[](#installation)

```
composer require mhrshuvo/journey-log
```

Server Setup &amp; Permissions
------------------------------

[](#server-setup--permissions)

### Required Permissions

[](#required-permissions)

Ensure your web server user has proper permissions:

```
# Set storage directory permissions
chmod -R 775 storage/logs
chown -R www-data:www-data storage/logs  # or your web server user

# For specific journey logs directory (after first use)
chmod -R 775 storage/logs/journeys
chown -R www-data:www-data storage/logs/journeys
```

### Common Server Issues &amp; Solutions

[](#common-server-issues--solutions)

#### 1. **Permission Denied Errors**

[](#1-permission-denied-errors)

If you see permission errors in logs:

```
# Fix storage permissions
sudo chmod -R 775 storage/
sudo chown -R www-data:www-data storage/

# Set proper umask for web server
# Add to your web server config: umask 002
```

#### 2. **SELinux Issues** (RHEL/CentOS)

[](#2-selinux-issues-rhelcentos)

```
# Allow web server to write to storage
sudo setsebool -P httpd_unified 1
sudo chcon -R -t httpd_exec_t storage/
```

#### 3. **Shared Hosting**

[](#3-shared-hosting)

- Ensure your hosting provider allows directory creation in storage/
- Some shared hosts require 755 permissions instead of 775
- Contact support if you can't create directories

#### 4. **Docker Container Issues**

[](#4-docker-container-issues)

```
# In your Dockerfile, ensure proper permissions
RUN chown -R www-data:www-data /var/www/storage
RUN chmod -R 775 /var/www/storage
```

### Automatic Cleanup Scheduler

[](#automatic-cleanup-scheduler)

The package includes automatic cleanup of old files. Ensure Laravel's scheduler is running:

```
# Add to crontab (crontab -e)
* * * * * cd /path-to-your-project && php artisan schedule:run >> /dev/null 2>&1
```

#### Manual Cleanup

[](#manual-cleanup)

```
# Test what would be cleaned (dry run)
php artisan journey-log:cleanup --dry-run

# Run cleanup manually
php artisan journey-log:cleanup

# Custom retention period (2 hours)
php artisan journey-log:cleanup --hours=2
```

Configuration
-------------

[](#configuration)

Publish the config file:

```
php artisan vendor:publish --tag=journeylog-config
```

### Environment Variables

[](#environment-variables)

Add to your `.env` file:

```
JOURNEY_LOG_STORAGE_PATH=journeys
JOURNEY_LOG_HEADER=X-Journey-ID
JOURNEY_LOG_SESSION_KEY=journey_id
JOURNEY_LOG_AUTO_CLEANUP_ENABLED=true
JOURNEY_LOG_CLEANUP_RETENTION_HOURS=1
```

### Data Masking

[](#data-masking)

Protect sensitive information by automatically masking specified field names in your logs. The package includes built-in protection for common sensitive fields and allows customization.

#### Default Protected Fields

[](#default-protected-fields)

The following fields are automatically masked by default:

- `password`
- `password_confirmation`
- `cvv`
- `card_number`
- `api_key`
- `auth_token`
- `access_token`
- `secret`

#### Usage Example

[](#usage-example)

```
// Original data with sensitive information
journey_log('payment', 'Processing payment', [
    'user_id' => 123,
    'amount' => 99.99,
    'card_number' => '1234-5678-9012-3456',  // Will be masked
    'cvv' => '123',                          // Will be masked
    'api_key' => 'sk_live_abc123xyz',        // Will be masked
    'transaction_id' => 'txn_456789'         // Will remain visible
]);
```

#### Generated Log Output

[](#generated-log-output)

```
[
  {
    "message": "Processing payment",
    "context": {
      "user_id": 123,
      "amount": 99.99,
      "card_number": "********",
      "cvv": "********",
      "api_key": "********",
      "transaction_id": "txn_456789"
    },
    "level": 200,
    "level_name": "INFO",
    "channel": "journey",
    "datetime": "2026-03-08T10:30:15+00:00",
    "extra": []
  }
]
```

#### Nested Data Masking

[](#nested-data-masking)

Masking works recursively through nested arrays:

```
journey_log('auth', 'User login attempt', [
    'user_data' => [
        'email' => 'user@example.com',
        'credentials' => [
            'password' => 'supersecret123',     // Will be masked
            'remember_token' => 'abc123'
        ]
    ],
    'request_info' => [
        'ip' => '192.168.1.1',
        'headers' => [
            'authorization' => 'Bearer secret'  // Will remain (not in mask_fields)
        ]
    ]
]);
```

#### Customizing Masked Fields

[](#customizing-masked-fields)

You can customize which fields are masked by modifying the configuration:

**config/journeylog.php:**

```
'mask_fields' => [
    'password',
    'password_confirmation',
    'cvv',
    'card_number',
    'api_key',
    'auth_token',
    'access_token',
    'secret',
    'ssn',              // Add custom sensitive field
    'bank_account',     // Add custom sensitive field
]
```

#### Case-Insensitive Matching

[](#case-insensitive-matching)

Field matching is case-insensitive, so all of these would be masked:

- `password`, `PASSWORD`, `Password`, `PaSsWoRd`
- `api_key`, `API_KEY`, `Api_Key`

Usage
-----

[](#usage)

### Basic Logging

[](#basic-logging)

```
// Log with folder organization
journey_log('checkout', 'User started checkout', ['total' => 99.99]);

// Log to root directory
journey_log(null, 'Page view', ['url' => '/products']);
```

### Example Use Cases

[](#example-use-cases)

#### **Example 1: Web Application User Journey**

[](#example-1-web-application-user-journey)

**Setup Route:**

```
// routes/web.php
Route::middleware(['web', 'journey-log'])->group(function () {
    Route::get('/products', [ProductController::class, 'index']);
    Route::post('/cart/add', [CartController::class, 'add']);
    Route::get('/checkout', [CheckoutController::class, 'index']);
    Route::post('/checkout/complete', [CheckoutController::class, 'complete']);
});
```

**Controller Implementation:**

```
class ProductController extends Controller
{
    public function index(Request $request)
    {
        // Log the user's product browsing
        journey_log('search', 'User viewed products page', [
            'category' => $request->get('category'),
            'user_agent' => $request->userAgent(),
            'ip' => $request->ip()
        ]);

        return view('products.index');
    }
}

class CartController extends Controller
{
    public function add(Request $request)
    {
        // Log add to cart action
        journey_log('cart', 'Product added to cart', [
            'product_id' => $request->product_id,
            'quantity' => $request->quantity,
            'price' => $request->price
        ]);

        return redirect()->back();
    }
}

class CheckoutController extends Controller
{
    public function complete(Request $request)
    {
        // Process order...

        // Log successful checkout
        journey_log('checkout', 'Order completed successfully', [
            'order_id' => $order->id,
            'total_amount' => $order->total,
            'payment_method' => $request->payment_method,
            'items_count' => $order->items->count()
        ]);

        return view('checkout.success');
    }
}
```

**Request Flow:**

```
1. User visits /products
   → Middleware generates: journey_id = "abc123def456"
   → Stored in session: journey_id = "abc123def456"
   → Log created: /storage/logs/journeys/search/journey-abc123def456.json

2. User adds product to cart (/cart/add)
   → Middleware reads from session: journey_id = "abc123def456"
   → Log appended to: /storage/logs/journeys/cart/journey-abc123def456.json

3. User completes checkout (/checkout/complete)
   → Same journey_id from session
   → Log appended to: /storage/logs/journeys/checkout/journey-abc123def456.json

```

#### **Example 2: API Application with Mobile App**

[](#example-2-api-application-with-mobile-app)

**Setup Route:**

```
// routes/api.php
Route::middleware(['api', 'journey-log'])->group(function () {
    Route::get('/products', [Api\ProductController::class, 'index']);
    Route::post('/cart', [Api\CartController::class, 'store']);
    Route::post('/orders', [Api\OrderController::class, 'store']);
});
```

**Mobile App Implementation:**

```
// Mobile app JavaScript
const journeyId = 'mobile-user-789xyz';

// Step 1: Browse products
const products = await fetch('/api/products', {
    headers: {
        'X-Journey-ID': journeyId,
        'Authorization': 'Bearer ' + authToken,
        'Content-Type': 'application/json'
    }
});

// Step 2: Add to cart
const cartResponse = await fetch('/api/cart', {
    method: 'POST',
    headers: {
        'X-Journey-ID': journeyId,
        'Authorization': 'Bearer ' + authToken,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        product_id: 123,
        quantity: 2
    })
});

// Step 3: Place order
const orderResponse = await fetch('/api/orders', {
    method: 'POST',
    headers: {
        'X-Journey-ID': journeyId,
        'Authorization': 'Bearer ' + authToken,
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        payment_method: 'credit_card'
    })
});
```

**API Controller Implementation:**

```
class Api\ProductController extends Controller
{
    public function index(Request $request)
    {
        // Log API product request
        journey_log('api-search', 'Mobile app browsed products', [
            'platform' => 'mobile',
            'app_version' => $request->header('App-Version'),
            'user_id' => auth()->id(),
            'filters' => $request->all()
        ]);

        return ProductResource::collection(Product::paginate());
    }
}

class Api\CartController extends Controller
{
    public function store(Request $request)
    {
        // Log add to cart via API
        journey_log('api-cart', 'Mobile user added item to cart', [
            'product_id' => $request->product_id,
            'quantity' => $request->quantity,
            'user_id' => auth()->id(),
            'platform' => 'mobile'
        ]);

        return response()->json(['success' => true]);
    }
}
```

**Request Flow:**

```
1. Mobile app calls /api/products
   → Header: X-Journey-ID = "mobile-user-789xyz"
   → Middleware reads header (no session in API)
   → Log created: /storage/logs/journeys/api-search/journey-mobile-user-789xyz.json

2. Mobile app calls /api/cart
   → Same X-Journey-ID header
   → Log appended to: /storage/logs/journeys/api-cart/journey-mobile-user-789xyz.json

3. Mobile app calls /api/orders
   → Same X-Journey-ID header
   → Log appended to: /storage/logs/journeys/api-orders/journey-mobile-user-789xyz.json

```

### Generated Log Files

[](#generated-log-files)

**Example: `/storage/logs/journeys/search/journey-abc123def456.json`**

```
[
  {
    "message": "User viewed products page",
    "context": {
      "category": "electronics",
      "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
      "ip": "192.168.1.100"
    },
    "level": 200,
    "level_name": "INFO",
    "channel": "journey",
    "datetime": "2026-02-28T10:30:15+00:00",
    "extra": []
  }
]
```

**Example: `/storage/logs/journeys/cart/journey-abc123def456.json`**

```
[
  {
    "message": "Product added to cart",
    "context": {
      "product_id": 456,
      "quantity": 2,
      "price": "29.99"
    },
    "level": 200,
    "level_name": "INFO",
    "channel": "journey",
    "datetime": "2026-02-28T10:32:45+00:00",
    "extra": []
  },
  {
    "message": "Product added to cart",
    "context": {
      "product_id": 789,
      "quantity": 1,
      "price": "15.50"
    },
    "level": 200,
    "level_name": "INFO",
    "channel": "journey",
    "datetime": "2026-02-28T10:35:12+00:00",
    "extra": []
  }
]
```

**Example: `/storage/logs/journeys/checkout/journey-abc123def456.json`**

```
[
  {
    "message": "Order completed successfully",
    "context": {
      "order_id": 12345,
      "total_amount": "75.48",
      "payment_method": "credit_card",
      "items_count": 3
    },
    "level": 200,
    "level_name": "INFO",
    "channel": "journey",
    "datetime": "2026-02-28T10:38:30+00:00",
    "extra": []
  }
]
```

### Middleware Setup

[](#middleware-setup)

```
// For Web routes - uses sessions + headers
Route::middleware(['web', 'journey-log'])->group(function () {
    Route::get('/checkout', [CheckoutController::class, 'index']);
    // Other web routes
});

// For API routes - uses headers
Route::middleware(['api', 'journey-log'])->group(function () {
    Route::get('/api/products', [ProductController::class, 'index']);
    // Other API routes
});
```

### Journey ID Resolution Priority

[](#journey-id-resolution-priority)

The middleware resolves journey IDs in this order for both web and API:

1. **Header**: `X-Journey-ID` (works for both web and API)
2. **Session**: `journey_id` (web routes only)
3. **Generated**: Random 12-character string (fallback)

### API Usage

[](#api-usage)

For API clients, include the journey ID in headers:

```
// JavaScript/Fetch
fetch('/api/products', {
    headers: {
        'X-Journey-ID': 'user-abc123',
        'Content-Type': 'application/json'
    }
});

// cURL
curl -H "X-Journey-ID: user-abc123" https://yourapp.com/api/products
```

### File Structure

[](#file-structure)

Files are organized as:

```
storage/logs/journeys/
├── checkout/
│   ├── journey-abc123.json
│   └── journey-def456.json
├── search/
│   └── journey-abc123.json
└── journey-guest.json

```

Troubleshooting
---------------

[](#troubleshooting)

### Check Permissions

[](#check-permissions)

```
# Verify storage is writable
ls -la storage/logs/

# Test file creation
touch storage/logs/test.txt && rm storage/logs/test.txt
```

### Enable Fallback Logging

[](#enable-fallback-logging)

The package automatically falls back to Laravel's default logging if it can't write journey files. Check your application logs:

```
tail -f storage/logs/laravel.log | grep JourneyLog
```

### Debug Mode

[](#debug-mode)

Enable debug mode to see permission issues:

```
# Run cleanup with verbose output
php artisan journey-log:cleanup --dry-run -v
```

Production Deployment Checklist
-------------------------------

[](#production-deployment-checklist)

- Storage directory exists and is writable
- Web server user owns storage directory
- Laravel scheduler is configured in cron
- SELinux contexts set (if applicable)
- Cleanup retention period configured appropriately
- Fallback logging is working (check Laravel logs)

Support
-------

[](#support)

If you encounter permission issues, the package will automatically log errors to Laravel's default log system for debugging.

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance87

Actively maintained with recent releases

Popularity7

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~5 days

Total

2

Last Release

65d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/895645bd09fc64b983f4790d14afc79688756f1740911666920744bc2d12df0b?d=identicon)[mhrshuvo](/maintainers/mhrshuvo)

---

Top Contributors

[![mhrshuvo](https://avatars.githubusercontent.com/u/57192512?v=4)](https://github.com/mhrshuvo "mhrshuvo (9 commits)")

---

Tags

middlewarelaravelloggingtrackinganalyticsjourneyuser-behaviorjson-logs

###  Code Quality

TestsPHPUnit

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/mhrshuvo-journey-log/health.svg)

```
[![Health](https://phpackages.com/badges/mhrshuvo-journey-log/health.svg)](https://phpackages.com/packages/mhrshuvo-journey-log)
```

###  Alternatives

[bugsnag/bugsnag-laravel

Official Bugsnag notifier for Laravel applications.

90234.6M36](/packages/bugsnag-bugsnag-laravel)[shaffe/laravel-mail-log-channel

A package to support logging via email in Laravel

1286.2k](/packages/shaffe-laravel-mail-log-channel)[moesif/moesif-laravel

Moesif Collection/Data Ingestion Middleware for Laravel

1065.8k](/packages/moesif-moesif-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
