Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V3 #3

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

V3 #3

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
os: [ubuntu-latest]
php: [8.4, 8.3]
laravel: [11.*]
stability: [prefer-lowest, prefer-stable]
stability: [prefer-stable]
include:
- laravel: 11.*
testbench: 9.*
Expand Down
62 changes: 29 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Automatically create GitHub issues from your Laravel exceptions & logs. Perfect
- 🏷️ Support customizable labels
- 🎯 Smart deduplication to prevent issue spam
- ⚡️ Buffered logging for better performance
- 📝 Customizable issue templates

## Showcase

Expand Down Expand Up @@ -103,51 +104,46 @@ Log::stack(['daily', 'github'])->error('Something went wrong!');

## Advanced Configuration

Deduplication and buffering are enabled by default to enhance logging. Customize these features to suit your needs.
### Customizing Templates

The package uses Markdown templates to format issues and comments. You can customize these templates by publishing them:

```bash
php artisan vendor:publish --tag="github-monolog-views"
```

This will copy the templates to `resources/views/vendor/github-monolog/` where you can modify them:

- `issue.md`: Template for new issues
- `comment.md`: Template for comments on existing issues
- `previous_exception.md`: Template for previous exceptions in the chain

Available template variables:
- `{level}`: Log level (error, warning, etc.)
- `{message}`: The error message or log content
- `{simplified_stack_trace}`: A cleaned up stack trace
- `{full_stack_trace}`: The complete stack trace
- `{previous_exceptions}`: Details of any previous exceptions
- `{context}`: Additional context data
- `{extra}`: Extra log data
- `{signature}`: Internal signature used for deduplication

### Deduplication

Group similar errors to avoid duplicate issues. By default, the package uses file-based storage. Customize the storage and time window to fit your application.
Group similar errors to avoid duplicate issues. The package uses Laravel's cache system for deduplication storage.

```php
'github' => [
// ... basic config from above ...
'deduplication' => [
'store' => 'file', // Default store
'time' => 60, // Time window in seconds
'time' => 60, // Time window in seconds - how long to wait before creating a new issue
'store' => null, // Uses your default cache store (from cache.default)
'prefix' => 'dedup', // Prefix for cache keys
],
]
```

#### Alternative Storage Options

Consider other storage options in these Laravel-specific scenarios:

- **Redis Store**: Use when:
- Running async queue jobs (file storage won't work across processes)
- Using Laravel Horizon for queue management
- Running multiple application instances behind a load balancer

```php
'deduplication' => [
'store' => 'redis',
'prefix' => 'github-monolog:',
'connection' => 'default', // Uses your Laravel Redis connection
],
```

- **Database Store**: Use when:
- Running queue jobs but Redis isn't available
- Need to persist deduplication data across deployments
- Want to query/debug deduplication history via database

```php
'deduplication' => [
'store' => 'database',
'table' => 'github_monolog_deduplication',
'connection' => null, // Uses your default database connection
],
```
For cache store configuration, refer to the [Laravel Cache documentation](https://laravel.com/docs/cache).

### Buffering

Expand Down
89 changes: 89 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Upgrade Guide

## Upgrading from 2.x to 3.0

### Breaking Changes

Version 3.0 introduces several breaking changes in how deduplication storage is handled:

1. **Removed Custom Store Implementations**
- FileStore, RedisStore, and DatabaseStore have been removed
- All deduplication storage now uses Laravel's cache system

2. **Configuration Changes**
- Store-specific configuration options have been removed
- New simplified cache-based configuration

### Migration Steps

1. **Update Package**
```bash
composer require naoray/laravel-github-monolog:^3.0
```

2. **Run Cleanup**
- Keep your old configuration in place
- Run the cleanup code above to remove old storage artifacts
- The cleanup code needs your old configuration to know what to clean up

3. **Update Configuration**
- Migrate to new store-specific configuration
- Add new cache-based configuration
- Configure Laravel cache as needed

### Configuration Updates

#### Before (2.x)
- [ ] ```php
'deduplication' => [
'store' => 'redis', // or 'file', 'database'
'connection' => 'default', // Redis/Database connection
'prefix' => 'github-monolog:', // Redis prefix
'table' => 'github_monolog_deduplication', // Database table
'time' => 60,
],
```

#### After (3.0)
```php
'deduplication' => [
'store' => null, // (optional) Uses Laravel's default cache store
'time' => 60, // Time window in seconds
'prefix' => 'dedup', // Cache key prefix
],
```

### Cleanup Code

Before updating your configuration to the new format, you should clean up artifacts from the 2.x version. The cleanup code uses your existing configuration to find and remove old storage:

```php
use Illuminate\Support\Facades\{Schema, Redis, File, DB};

// Get your current config
$config = config('logging.channels.github.deduplication', []);
$store = $config['store'] ?? 'file';

if ($store === 'database') {
// Clean up database table using your configured connection and table name
$connection = $config['connection'] ?? config('database.default');
$table = $config['table'] ?? 'github_monolog_deduplication';

Schema::connection($connection)->dropIfExists($table);
}

if ($store === 'redis') {
// Clean up Redis entries using your configured connection and prefix
$connection = $config['connection'] ?? 'default';
$prefix = $config['prefix'] ?? 'github-monolog:';
Redis::connection($connection)->del($prefix . 'dedup');
}

if ($store === 'file') {
// Clean up file storage using your configured path
$path = $config['path'] ?? storage_path('logs/github-monolog-deduplication.log');
if (File::exists($path)) {
File::delete($path);
}
}
```
11 changes: 10 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"illuminate/http": "^11.0",
"illuminate/support": "^11.0",
"illuminate/filesystem": "^11.0",
"monolog/monolog": "^3.6"
"illuminate/cache": "^11.0",
"monolog/monolog": "^3.6",
"laravel/prompts": "^0.3.3"
},
"require-dev": {
"laravel/pint": "^1.14",
Expand Down Expand Up @@ -62,6 +64,13 @@
"phpstan/extension-installer": true
}
},
"extra": {
"laravel": {
"providers": [
"Naoray\\LaravelGithubMonolog\\GithubMonologServiceProvider"
]
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
28 changes: 28 additions & 0 deletions resources/views/comment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# New Occurrence

**Log Level:** {level}

{message}

**Simplified Stack Trace:**
```php
{simplified_stack_trace}
```

<details>
<summary>Complete Stack Trace</summary>

```php
{full_stack_trace}
```
</details>

<details>
<summary>Previous Exceptions</summary>

{previous_exceptions}
</details>

{context}

{extra}
28 changes: 28 additions & 0 deletions resources/views/issue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
**Log Level:** {level}

{message}

**Simplified Stack Trace:**
```php
{simplified_stack_trace}
```

<details>
<summary>Complete Stack Trace</summary>

```php
{full_stack_trace}
```
</details>

<details>
<summary>Previous Exceptions</summary>

{previous_exceptions}
</details>

{context}

{extra}

<!-- Signature: {signature} -->
15 changes: 15 additions & 0 deletions resources/views/previous_exception.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
### Previous Exception #{count}
**Type:** {type}

**Simplified Stack Trace:**
```php
{simplified_stack_trace}
```

<details>
<summary>Complete Stack Trace</summary>

```php
{full_stack_trace}
```
</details>
68 changes: 68 additions & 0 deletions src/Deduplication/CacheManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace Naoray\LaravelGithubMonolog\Deduplication;

use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;

class CacheManager
{
private const KEY_PREFIX = 'github-monolog';

private const KEY_SEPARATOR = ':';

private readonly string $store;

private readonly Repository $cache;

public function __construct(
?string $store = null,
private readonly string $prefix = 'dedup',
private readonly int $ttl = 60
) {
$this->store = $store ?? config('cache.default');
$this->cache = Cache::store($this->store);
}

public function has(string $signature): bool
{
return $this->cache->has($this->composeKey($signature));
}

public function add(string $signature): void
{
$this->cache->put(
$this->composeKey($signature),
Carbon::now()->timestamp,
$this->ttl
);
}

/**
* Clear all entries for the current prefix.
* Note: This is a best-effort operation and might not work with all cache stores.
*/
public function clear(): void
{
// For Redis/Memcached stores that support tag-like operations
if (method_exists($this->cache->getStore(), 'flush')) {
$this->cache->getStore()->flush();

return;
}

// For other stores, we'll have to rely on TTL cleanup
// You might want to implement a more specific cleanup strategy
// based on your cache store if needed
}

private function composeKey(string $signature): string
{
return implode(self::KEY_SEPARATOR, [
self::KEY_PREFIX,
$this->prefix,
$signature,
]);
}
}
15 changes: 10 additions & 5 deletions src/Deduplication/DeduplicationHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
use Monolog\Handler\HandlerInterface;
use Monolog\Level;
use Monolog\LogRecord;
use Naoray\LaravelGithubMonolog\Deduplication\Stores\StoreInterface;

class DeduplicationHandler extends BufferHandler
{
private CacheManager $cache;

public function __construct(
HandlerInterface $handler,
protected StoreInterface $store,
protected SignatureGeneratorInterface $signatureGenerator,
string $store = 'default',
string $prefix = 'github-monolog:dedup:',
int $ttl = 60,
int|string|Level $level = Level::Error,
int $bufferLimit = 0,
bool $bubble = true,
Expand All @@ -27,6 +30,8 @@ public function __construct(
bubble: $bubble,
flushOnOverflow: $flushOnOverflow,
);

$this->cache = new CacheManager($store, $prefix, $ttl);
}

public function flush(): void
Expand All @@ -42,12 +47,12 @@ public function flush(): void
// Create new record with signature in extra data
$record = $record->with(extra: ['github_issue_signature' => $signature] + $record->extra);

// If the record is a duplicate, we don't want to add it to the store
if ($this->store->isDuplicate($record, $signature)) {
// If the record is a duplicate, we don't want to process it
if ($this->cache->has($signature)) {
return null;
}

$this->store->add($record, $signature);
$this->cache->add($signature);

return $record;
})
Expand Down
Loading