Skip to content

Commit 14c3772

Browse files
Merge pull request #4 from esign/feature/improve-logging
feat: improve logging and add HasFactory to Shop model
2 parents 01d278e + b767d7f commit 14c3772

22 files changed

Lines changed: 427 additions & 309 deletions

config/shopify.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@
4040
// Number of minutes before token expiration to consider it "expiring soon"
4141
// Used by Shop::isAccessTokenExpiringSoon() helper method
4242
'buffer_minutes' => 5,
43-
44-
// Whether to log token lifecycle events (refresh, expiration, etc.)
45-
'log_token_lifecycle' => env('SHOPIFY_LOG_TOKEN_LIFECYCLE', true),
4643
],
4744

4845
/*
@@ -108,13 +105,26 @@
108105
|--------------------------------------------------------------------------
109106
| Logging Configuration
110107
|--------------------------------------------------------------------------
108+
|
109+
| Hierarchical logging configuration with master switch and category flags.
110+
| The 'enabled' flag is the master switch - when false, NO logs are written.
111+
| Category-specific flags only apply when 'enabled' is true.
112+
|
111113
*/
112114

113115
'logging' => [
116+
// Master switch - disables ALL Shopify logging when false
114117
'enabled' => env('SHOPIFY_LOGGING_ENABLED', true),
118+
119+
// Log channel to use for all Shopify logs
115120
'channel' => env('SHOPIFY_LOG_CHANNEL', 'stack'),
116-
'log_queries' => true, // Log all GraphQL queries
117-
'log_mutations' => true, // Log all GraphQL mutations
118-
'log_webhooks' => true, // Log webhook dispatch
121+
122+
// Category-specific flags (only apply when 'enabled' is true)
123+
'log_graphql_queries' => env('SHOPIFY_LOG_GRAPHQL_QUERIES', true),
124+
'log_graphql_mutations' => env('SHOPIFY_LOG_GRAPHQL_MUTATIONS', true),
125+
'log_webhooks' => env('SHOPIFY_LOG_WEBHOOKS', true),
126+
'log_token_lifecycle' => env('SHOPIFY_LOG_TOKEN_LIFECYCLE', true),
127+
'log_shop_lifecycle' => env('SHOPIFY_LOG_SHOP_LIFECYCLE', true),
128+
'log_gdpr_events' => env('SHOPIFY_LOG_GDPR_EVENTS', true),
119129
],
120130
];

src/Auth/SessionTokenHandler.php

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
namespace Esign\LaravelShopify\Auth;
44

5-
use Illuminate\Support\Facades\Log;
5+
use Esign\LaravelShopify\Support\LogCategory;
6+
use Esign\LaravelShopify\Support\ShopifyLogger;
67
use Shopify\App\ShopifyApp;
78

89
/**
@@ -78,23 +79,19 @@ public function exchangeForAccessToken(
7879
throw new \RuntimeException('Token exchange failed: '.($result->log->message ?? 'Unknown error'));
7980
}
8081

81-
if (config('shopify.logging.enabled')) {
82-
Log::info('Token exchange successful', [
83-
'shop' => $result->shop ?? 'unknown',
84-
'token_type' => $tokenType,
85-
'has_token' => ! empty($result->accessToken),
86-
]);
87-
}
82+
ShopifyLogger::log(LogCategory::TokenLifecycle)->info('Token exchange successful', [
83+
'shop' => $result->shop ?? 'unknown',
84+
'token_type' => $tokenType,
85+
'has_token' => ! empty($result->accessToken),
86+
]);
8887

8988
return $result;
9089

9190
} catch (\Exception $e) {
92-
if (config('shopify.logging.enabled')) {
93-
Log::error('Token exchange failed', [
94-
'error' => $e->getMessage(),
95-
'token_type' => $tokenType,
96-
]);
97-
}
91+
ShopifyLogger::log(LogCategory::TokenLifecycle)->error('Token exchange failed', [
92+
'error' => $e->getMessage(),
93+
'token_type' => $tokenType,
94+
]);
9895

9996
throw new \RuntimeException("Failed to exchange session token for access token: {$e->getMessage()}");
10097
}

src/Auth/TokenRefreshService.php

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
namespace Esign\LaravelShopify\Auth;
44

55
use Esign\LaravelShopify\Models\Shop;
6-
use Illuminate\Support\Facades\Log;
6+
use Esign\LaravelShopify\Support\LogCategory;
7+
use Esign\LaravelShopify\Support\ShopifyLogger;
78
use Shopify\App\ShopifyApp;
89

910
/**
@@ -39,7 +40,7 @@ public function refreshAccessToken(Shop $shop): bool
3940
try {
4041
// Validate shop has necessary data
4142
if (! $shop->refresh_token) {
42-
Log::error('Cannot refresh token: no refresh token', [
43+
ShopifyLogger::log(LogCategory::TokenLifecycle)->error('Cannot refresh token: no refresh token', [
4344
'shop' => $shop->domain,
4445
]);
4546

@@ -48,7 +49,7 @@ public function refreshAccessToken(Shop $shop): bool
4849

4950
// Check if refresh token is expired (pre-validation)
5051
if ($shop->isRefreshTokenExpired()) {
51-
Log::warning('Refresh token expired, clearing tokens', [
52+
ShopifyLogger::log(LogCategory::TokenLifecycle)->warning('Refresh token expired, clearing tokens', [
5253
'shop' => $shop->domain,
5354
'refresh_token_expires_at' => $shop->refresh_token_expires_at,
5455
]);
@@ -57,7 +58,7 @@ public function refreshAccessToken(Shop $shop): bool
5758
return false;
5859
}
5960

60-
Log::info('Attempting token refresh', [
61+
ShopifyLogger::log(LogCategory::TokenLifecycle)->info('Attempting token refresh', [
6162
'shop' => $shop->domain,
6263
'access_token_expires_at' => $shop->access_token_expires_at,
6364
]);
@@ -73,15 +74,15 @@ public function refreshAccessToken(Shop $shop): bool
7374

7475
// Check result
7576
if (! $result->ok) {
76-
Log::error('Token refresh failed', [
77+
ShopifyLogger::log(LogCategory::TokenLifecycle)->error('Token refresh failed', [
7778
'shop' => $shop->domain,
7879
'error_code' => $result->log->code,
7980
'error_detail' => $result->log->detail,
8081
]);
8182

8283
// If refresh token is invalid/expired, clear tokens
8384
if (in_array($result->log->code, ['invalid_grant', 'refresh_token_expired'])) {
84-
Log::warning('Refresh token invalid, clearing all tokens', [
85+
ShopifyLogger::log(LogCategory::TokenLifecycle)->warning('Refresh token invalid, clearing all tokens', [
8586
'shop' => $shop->domain,
8687
]);
8788
$this->clearTokens($shop);
@@ -92,7 +93,7 @@ public function refreshAccessToken(Shop $shop): bool
9293

9394
// Check if library says token is still valid (no refresh needed)
9495
if ($result->log->code === 'token_still_valid') {
95-
Log::info('Token still valid, no refresh needed', [
96+
ShopifyLogger::log(LogCategory::TokenLifecycle)->info('Token still valid, no refresh needed', [
9697
'shop' => $shop->domain,
9798
]);
9899

@@ -111,15 +112,15 @@ public function refreshAccessToken(Shop $shop): bool
111112
'access_token_last_refreshed_at' => now(),
112113
]);
113114

114-
Log::info('Token refresh successful', [
115+
ShopifyLogger::log(LogCategory::TokenLifecycle)->info('Token refresh successful', [
115116
'shop' => $shop->domain,
116117
'new_expires_at' => $newAccessToken->expires,
117118
]);
118119

119120
return true;
120121

121122
} catch (\Exception $e) {
122-
Log::error('Token refresh exception', [
123+
ShopifyLogger::log(LogCategory::TokenLifecycle)->error('Token refresh exception', [
123124
'shop' => $shop->domain,
124125
'error' => $e->getMessage(),
125126
'trace' => $e->getTraceAsString(),
@@ -137,7 +138,7 @@ public function refreshAccessToken(Shop $shop): bool
137138
*/
138139
public function clearTokens(Shop $shop): void
139140
{
140-
Log::info('Clearing tokens', ['shop' => $shop->domain]);
141+
ShopifyLogger::log(LogCategory::TokenLifecycle)->info('Clearing tokens', ['shop' => $shop->domain]);
141142

142143
$shop->update([
143144
'access_token' => null,

src/Console/stubs/webhook-job.stub

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use Illuminate\Contracts\Queue\ShouldQueue;
88
use Illuminate\Foundation\Bus\Dispatchable;
99
use Illuminate\Queue\InteractsWithQueue;
1010
use Illuminate\Queue\SerializesModels;
11-
use Illuminate\Support\Facades\Log;
11+
use Esign\LaravelShopify\Support\LogCategory;
12+
use Esign\LaravelShopify\Support\ShopifyLogger;
1213

1314
class {{ class }} implements ShouldQueue
1415
{
@@ -21,7 +22,7 @@ class {{ class }} implements ShouldQueue
2122

2223
public function handle(): void
2324
{
24-
Log::info('{{ topic }} webhook received', [
25+
ShopifyLogger::log(LogCategory::Webhooks)->info('{{ topic }} webhook received', [
2526
'shop' => $this->shopDomain,
2627
'webhook_topic' => '{{ topic }}',
2728
]);
@@ -30,7 +31,7 @@ class {{ class }} implements ShouldQueue
3031
$shop = Shop::where('domain', $this->shopDomain)->first();
3132

3233
if (! $shop) {
33-
Log::warning('Shop not found for {{ topic }} webhook', [
34+
ShopifyLogger::log(LogCategory::Webhooks)->warning('Shop not found for {{ topic }} webhook', [
3435
'shop' => $this->shopDomain,
3536
]);
3637

@@ -39,14 +40,14 @@ class {{ class }} implements ShouldQueue
3940

4041
// TODO: Implement your webhook handling logic here
4142

42-
Log::info('{{ topic }} webhook processed', [
43+
ShopifyLogger::log(LogCategory::Webhooks)->info('{{ topic }} webhook processed', [
4344
'shop' => $this->shopDomain,
4445
]);
4546
}
4647

4748
public function failed(\Throwable $exception): void
4849
{
49-
Log::error('{{ topic }} webhook job failed', [
50+
ShopifyLogger::log(LogCategory::Webhooks)->error('{{ topic }} webhook job failed', [
5051
'shop' => $this->shopDomain,
5152
'error' => $exception->getMessage(),
5253
]);

src/Exceptions/ShopifyAuthenticationExceptionHandler.php

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
namespace Esign\LaravelShopify\Exceptions;
44

5+
use Esign\LaravelShopify\Support\LogCategory;
6+
use Esign\LaravelShopify\Support\ShopifyLogger;
57
use Illuminate\Http\Request;
6-
use Illuminate\Support\Facades\Log;
78

89
/**
910
* Handler for rendering Shopify authentication exceptions.
@@ -24,14 +25,12 @@ class ShopifyAuthenticationExceptionHandler
2425
public function render(ShopifyAuthenticationException $exception, Request $request)
2526
{
2627
// Log the authentication failure
27-
if (config('shopify.logging.enabled')) {
28-
Log::warning('Shopify authentication failed', [
29-
'type' => $exception->getRequestType(),
30-
'reason' => $exception->getReason(),
31-
'shop' => $exception->getShopDomain(),
32-
'url' => $request->fullUrl(),
33-
]);
34-
}
28+
ShopifyLogger::log(LogCategory::TokenLifecycle)->warning('Shopify authentication failed', [
29+
'type' => $exception->getRequestType(),
30+
'reason' => $exception->getReason(),
31+
'shop' => $exception->getShopDomain(),
32+
'url' => $request->fullUrl(),
33+
]);
3534

3635
// Handle embedded app requests
3736
if ($exception->isEmbeddedAppRequest()) {
@@ -78,13 +77,11 @@ public function render(ShopifyAuthenticationException $exception, Request $reque
7877
public function renderTokenRefreshRequired(TokenRefreshRequiredException $exception, Request $request)
7978
{
8079
// Log the token refresh failure
81-
if (config('shopify.logging.enabled')) {
82-
Log::warning('Token refresh required', [
83-
'shop' => $exception->shop->domain,
84-
'url' => $request->fullUrl(),
85-
'message' => $exception->getMessage(),
86-
]);
87-
}
80+
ShopifyLogger::log(LogCategory::TokenLifecycle)->warning('Token refresh required', [
81+
'shop' => $exception->shop->domain,
82+
'url' => $request->fullUrl(),
83+
'message' => $exception->getMessage(),
84+
]);
8885

8986
// For embedded apps (XHR), return JSON with requiresRefresh flag
9087
// Frontend should catch this and reload the page

src/GraphQL/Client.php

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@
55
use Esign\LaravelShopify\Auth\TokenRefreshService;
66
use Esign\LaravelShopify\Exceptions\TokenRefreshRequiredException;
77
use Esign\LaravelShopify\GraphQL\Concerns\HandlesGraphQLErrors;
8-
use Esign\LaravelShopify\GraphQL\Concerns\LogsGraphQLOperations;
98
use Esign\LaravelShopify\GraphQL\Contracts\Mutation;
109
use Esign\LaravelShopify\GraphQL\Contracts\PaginatedQuery;
1110
use Esign\LaravelShopify\GraphQL\Contracts\Query;
1211
use Esign\LaravelShopify\Models\Shop;
13-
use Illuminate\Support\Facades\Log;
12+
use Esign\LaravelShopify\Support\LogCategory;
13+
use Esign\LaravelShopify\Support\ShopifyLogger;
1414
use Shopify\App\ShopifyApp;
1515
use Shopify\App\Types\GQLResult;
1616

1717
class Client
1818
{
19-
use HandlesGraphQLErrors, LogsGraphQLOperations;
19+
use HandlesGraphQLErrors;
2020

2121
protected ShopifyApp $shopifyApp;
2222

@@ -34,7 +34,7 @@ public function __construct(
3434
*/
3535
public function query(Query $query): mixed
3636
{
37-
$this->logOperation('query', $query->query(), $query->variables());
37+
$this->logOperation('query', LogCategory::GraphqlQueries, $query->query(), $query->variables());
3838

3939
$response = $this->executeGraphQL($query->query(), $query->variables());
4040

@@ -46,7 +46,7 @@ public function query(Query $query): mixed
4646
*/
4747
public function mutation(Mutation $mutation): mixed
4848
{
49-
$this->logOperation('mutation', $mutation->query(), $mutation->variables());
49+
$this->logOperation('mutation', LogCategory::GraphqlMutations, $mutation->query(), $mutation->variables());
5050

5151
$response = $this->executeGraphQL($mutation->query(), $mutation->variables());
5252

@@ -61,7 +61,7 @@ public function queryPaginated(PaginatedQuery $query): array
6161
$results = [];
6262

6363
do {
64-
$this->logOperation('query', $query->query(), $query->variables());
64+
$this->logOperation('query', LogCategory::GraphqlQueries, $query->query(), $query->variables());
6565

6666
$response = $this->executeGraphQL($query->query(), $query->variables());
6767

@@ -86,14 +86,14 @@ protected function executeGraphQL(string $query, array $variables = []): GQLResu
8686

8787
// Check for authentication errors (retriable)
8888
if (! $result->ok && $this->isAuthenticationError($result)) {
89-
Log::info('GraphQL authentication error detected, attempting token refresh', [
89+
ShopifyLogger::log(LogCategory::TokenLifecycle)->info('GraphQL authentication error detected, attempting token refresh', [
9090
'shop' => $this->shop->domain,
9191
'error_code' => $result->log->code,
9292
'error_detail' => $result->log->detail,
9393
]);
9494

9595
if ($this->attemptTokenRefresh()) {
96-
Log::info('Token refresh successful, retrying GraphQL request', [
96+
ShopifyLogger::log(LogCategory::TokenLifecycle)->info('Token refresh successful, retrying GraphQL request', [
9797
'shop' => $this->shop->domain,
9898
]);
9999

@@ -153,7 +153,7 @@ protected function attemptTokenRefresh(): bool
153153

154154
return false;
155155
} catch (\Exception $e) {
156-
Log::error('Token refresh attempt failed', [
156+
ShopifyLogger::log(LogCategory::TokenLifecycle)->error('Token refresh attempt failed', [
157157
'shop' => $this->shop->domain,
158158
'error' => $e->getMessage(),
159159
]);
@@ -191,4 +191,16 @@ protected function isAuthenticationError(GQLResult $result): bool
191191

192192
return false;
193193
}
194+
195+
/**
196+
* Log a GraphQL operation if its category flag is enabled.
197+
*/
198+
protected function logOperation(string $type, LogCategory $category, string $query, array $variables): void
199+
{
200+
ShopifyLogger::log($category)->info("GraphQL {$type} executed", [
201+
'shop' => $this->shop->domain,
202+
'query' => $query,
203+
'variables' => $variables,
204+
]);
205+
}
194206
}

src/GraphQL/Concerns/LogsGraphQLOperations.php

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)