תארו לעצמכם: ייבוא לילי של 50,000 מוצרים מ-1C. בבוקר, המנהל רואה שמחירים של 200 פריטים השתבשו. ללא לוגים — יום של חיפוש ידני. עם המערכת שלנו — 5 דקות לניתוח הדוח. לוג טוב לוכד כל שלב, והדוח נותן תשובה חד-משמעית: מה קרה, מה השתנה, מה נשבר.
אנו מיישמים מערכת לוגים לייבוא מוצרים במפתח מלא. הניסיון שלנו — מעל 50 פרויקטים באינטגרציה ותהליכי ETL לחנות מקוונת. נבחן את הפרויקט שלכם ונציע פתרון תוך יום אחד. המערכת תקצר את זמן איתור הבעיות ב-80% ותספק שקיפות מלאה לתהליך. הפסדים אופייניים משגיאות ייבוא בחנות עם 10,000 מוצרים מגיעים עד $900–1.3k בחודש — הפתרון שלנו ממזער אותם. במאמר זה נסביר כיצד להגדיר לוגים עם חציצה (buffering), מעקב שינויים והתראות.
כיצד פועלת מערכת הלוגים לייבוא
אילו נתונים יש לרשום בלוג במהלך הייבוא?
ברמת ריצת הייבוא:
- זמני התחלה וסיום
- מקור נתונים, סוג, כתובת URL/נתיב קובץ
- מונה סופי: נוצר / עודכן / דולג / שגיאות
- סטטוס: הצלחה / חלקי / נכשל
- המשתמש שהפעיל את הייבוא (אם ידני)
ברמת שורה בודדת:
- מספר שורה / SKU
- סוג פעולה: יצירה / עדכון / דילוג / שגיאה
- שדות שהשתנו (diff)
- הודעת שגיאה (אם קיימת)
סכמת מסד נתונים ללוגים
CREATE TABLE import_runs (
id serial PRIMARY KEY,
source_id int REFERENCES import_sources(id),
status varchar(20) DEFAULT 'pending', -- pending | processing | success | partial | failed
trigger varchar(20) DEFAULT 'scheduled', -- scheduled | manual | webhook
triggered_by int REFERENCES users(id),
file_name varchar(500),
file_size bigint,
total_rows int DEFAULT 0,
created_count int DEFAULT 0,
updated_count int DEFAULT 0,
skipped_count int DEFAULT 0,
errors_count int DEFAULT 0,
started_at timestamptz,
completed_at timestamptz,
duration_ms int,
error_message text,
created_at timestamptz DEFAULT now()
);
CREATE TABLE import_row_logs (
id bigserial PRIMARY KEY,
import_id int REFERENCES import_runs(id) ON DELETE CASCADE,
line_number int,
sku varchar(100),
operation varchar(10), -- create | update | skip | error
changed_fields jsonb, -- {"price": {"old": 100, "new": 120}}
error_code varchar(50),
error_msg text,
created_at timestamptz DEFAULT now()
);
CREATE INDEX import_row_logs_import_id_idx ON import_row_logs (import_id);
CREATE INDEX import_row_logs_sku_idx ON import_row_logs (sku);
מימוש לוגר עם חציצה ו-diff
המחלקה הראשית CREATE TABLE import_runs ( id serial PRIMARY KEY, source_id int REFERENCES import_sources(id), status varchar(20) DEFAULT 'pending', -- pending | processing | success | partial | failed trigger varchar(20) DEFAULT 'scheduled', -- scheduled | manual | webhook triggered_by int REFERENCES users(id), file_name varchar(500), file_size bigint, total_rows int DEFAULT 0, created_count int DEFAULT 0, updated_count int DEFAULT 0, skipped_count int DEFAULT 0, errors_count int DEFAULT 0, started_at timestamptz, completed_at timestamptz, duration_ms int, error_message text, created_at timestamptz DEFAULT now() ); CREATE TABLE import_row_logs ( id bigserial PRIMARY KEY, import_id int REFERENCES import_runs(id) ON DELETE CASCADE, line_number int, sku varchar(100), operation varchar(10), -- create | update | skip | error changed_fields jsonb, -- {"price": {"old": 100, "new": 120}} error_code varchar(50), error_msg text, created_at timestamptz DEFAULT now() ); CREATE INDEX import_row_logs_import_id_idx ON import_row_logs (import_id); CREATE INDEX import_row_logs_sku_idx ON import_row_logs (sku); מנהלת כתיבת לוגים. חציצה של רשומות בקבוצות של 500 — במקום INSERT לכל שורה, מפחיתה את העומס על מסד הנתונים פי 10 בייבוא סטנדרטי.
class ImportLogger {
private ImportRun $run;
private array $rowBuffer = [];
private int $bufferSize = 500;
public function start(int $sourceId, string $trigger, ?int $userId): void
{
$this->run = ImportRun::create([
'source_id' => $sourceId,
'status' => 'processing',
'trigger' => $trigger,
'triggered_by' => $userId,
'started_at' => now(),
]);
}
public function logRow(
int $line,
string $sku,
string $operation,
array $changedFields = [],
?string $errorMsg = null,
?string $errorCode = null
): void {
$this->rowBuffer[] = [
'import_id' => $this->run->id,
'line_number' => $line,
'sku' => $sku,
'operation' => $operation,
'changed_fields'=> $changedFields ? json_encode($changedFields) : null,
'error_code' => $errorCode,
'error_msg' => $errorMsg,
'created_at' => now()->toDateTimeString(),
];
if (count($this->rowBuffer) >= $this->bufferSize) {
$this->flush();
}
}
public function finish(string $status, ?string $errorMessage = null): void
{
$this->flush();
$counts = DB::table('import_row_logs')
->where('import_id', $this->run->id)
->selectRaw("
SUM(CASE WHEN operation = 'create' THEN 1 ELSE 0 END) AS created,
SUM(CASE WHEN operation = 'update' THEN 1 ELSE 0 END) AS updated,
SUM(CASE WHEN operation = 'skip' THEN 1 ELSE 0 END) AS skipped,
SUM(CASE WHEN operation = 'error' THEN 1 ELSE 0 END) AS errors
")
->first();
$this->run->update([
'status' => $status,
'created_count' => $counts->created,
'updated_count' => $counts->updated,
'skipped_count' => $counts->skipped,
'errors_count' => $counts->errors,
'completed_at' => now(),
'duration_ms' => now()->diffInMilliseconds($this->run->started_at),
'error_message' => $errorMessage,
]);
}
private function flush(): void
{
if (!empty($this->rowBuffer)) {
DB::table('import_row_logs')->insert($this->rowBuffer);
$this->rowBuffer = [];
}
}
}מעקב שינויים (diff) עוקב אחר שדות ספציפיים: מחיר, מלאי, שם, תיאור. המתודה ImportLogger מחזירה מערך של שינויים, שנכתב ל-class ImportLogger { private ImportRun $run; private array $rowBuffer = []; private int $bufferSize = 500; public function start(int $sourceId, string $trigger, ?int $userId): void { $this->run = ImportRun::create([ 'source_id' => $sourceId, 'status' => 'processing', 'trigger' => $trigger, 'triggered_by' => $userId, 'started_at' => now(), ]); } public function logRow( int $line, string $sku, string $operation, array $changedFields = [], ?string $errorMsg = null, ?string $errorCode = null ): void { $this->rowBuffer[] = [ 'import_id' => $this->run->id, 'line_number' => $line, 'sku' => $sku, 'operation' => $operation, 'changed_fields'=> $changedFields ? json_encode($changedFields) : null, 'error_code' => $errorCode, 'error_msg' => $errorMsg, 'created_at' => now()->toDateTimeString(), ]; if (count($this->rowBuffer) >= $this->bufferSize) { $this->flush(); } } public function finish(string $status, ?string $errorMessage = null): void { $this->flush(); $counts = DB::table('import_row_logs') ->where('import_id', $this->run->id) ->selectRaw(" SUM(CASE WHEN operation = 'create' THEN 1 ELSE 0 END) AS created, SUM(CASE WHEN operation = 'update' THEN 1 ELSE 0 END) AS updated, SUM(CASE WHEN operation = 'skip' THEN 1 ELSE 0 END) AS skipped, SUM(CASE WHEN operation = 'error' THEN 1 ELSE 0 END) AS errors ") ->first(); $this->run->update([ 'status' => $status, 'created_count' => $counts->created, 'updated_count' => $counts->updated, 'skipped_count' => $counts->skipped, 'errors_count' => $counts->errors, 'completed_at' => now(), 'duration_ms' => now()->diffInMilliseconds($this->run->started_at), 'error_message' => $errorMessage, ]); } private function flush(): void { if (!empty($this->rowBuffer)) { DB::table('import_row_logs')->insert($this->rowBuffer); $this->rowBuffer = []; } } } .
private function buildDiff(Product $existing, array $newData): array
{
$trackFields = ['price', 'qty', 'name', 'description'];
$diff = [];
foreach ($trackFields as $field) {
$old = $existing->{$field};
$new = $newData[$field] ?? null;
if ((string) $old !== (string) $new) {
$diff[$field] = ['old' => $old, 'new' => $new];
}
}
return $diff;
} למה חשוב לתעד diff?
ללא diff, לא תדעו מה השתנה במוצר: המחיר ירד ב-20% או שהתיאור התערבב. דוח מצטבר מראה כמה פריטים הושפעו בכל שדה. המנהל רואה מיד את היקף השינויים — זה מהיר פי עשרות מבדיקה ידנית. המערכת שלנו חוסכת עד 80% מזמן הניפוי, ששווה ערך ל-$360–520 בחודש.
דוחות והתראות
דוח מצטבר
המחלקה buildDiff מייצרת דוח עם 10 קודי השגיאה המובילים וספירת שינויים לפי שדה.
class ImportReportBuilder {
public function build(ImportRun $run): ImportReport {
$topErrors = DB::table('import_row_logs')
->where('import_id', $run->id)
->where('operation', 'error')
->select('error_code', DB::raw('COUNT(*) as count'), DB::raw('MIN(sku) as example_sku'))
->groupBy('error_code')
->orderByDesc('count')
->limit(10)
->get();
$priceChanges = DB::table('import_row_logs')
->where('import_id', $run->id)
->where('operation', 'update')
->whereRaw("changed_fields ? 'price'")
->count();
return new ImportReport(
run: $run,
topErrors: $topErrors,
priceChangesCount: $priceChanges,
);
}
} התראות על תוצאות
ההתראה נשלחת רק אם הסטטוס אינו changed_fields או שמספר השגיאות עובר סף מסוים. האימייל מכיל סיכום וקישור לדוח.
class ImportCompletedNotification extends Notification
{
public function toMail(mixed $notifiable): MailMessage
{
$run = $this->run;
return (new MailMessage)
->subject("Импорт #{$run->id}: {$run->status}")
->line("Источник: {$run->source->name}")
->line("Создано: {$run->created_count}, обновлено: {$run->updated_count}, ошибок: {$run->errors_count}")
->line("Длительность: " . round($run->duration_ms / 1000, 1) . " сек")
->when($run->errors_count > 0, fn($m) => $m->action('Просмотреть ошибки', $this->reportUrl()));
}
} איך התראות עוזרות למנוע משבר?
ההתראה מגיעה באימייל רק על ייבוא חלקי או נכשל. האימייל מכיל סיכום: נוצר, עודכן, שגיאות, משך זמן. אם אין שגיאות, המנהל לא מוסח. אם יש שגיאות, הם עוברים לדוח ורואים 10 קודי השגיאה המובילים עם דוגמאות SKU.
סיבוב לוגים
לוגים ברמת שורה גדלים במהירות. מדיניות שמירה: מחיקת פרטי ייבוא מוצלח ישן פעם בחודש. רשומות סיכום של import_run נשמרות לצמיתות — הן תופסות מעט מקום. פקודת Artisan private function buildDiff(Product $existing, array $newData): array { $trackFields = ['price', 'qty', 'name', 'description']; $diff = []; foreach ($trackFields as $field) { $old = $existing->{$field}; $new = $newData[$field] ?? null; if ((string) $old !== (string) $new) { $diff[$field] = ['old' => $old, 'new' => $new]; } } return $diff; } רצה שבועית לפי לוח זמנים.
השוואת גישות לרישום לוגים
| גישה | ביצועים | פירוט | ביקורת |
|---|---|---|---|
| רישום שורות פשוט | גבוה | נמוך (ללא צבירה) | ללא diff |
| רישום עם חציצה ו-diff | גבוה (חציצה של 500 שורות) | גבוה (כל שורה, diff) | מספיק לחנות |
| טבלת שינויים היסטורית מלאה | נמוך (ללא חלוקה) | מלא | מלא אך יקר |
אנו בוחרים בגישה עם חציצה — היא לא מעמיסה על מסד הנתונים ומספקת תמונה מפורטת לכל שורה.
מה כלול בעבודה
- עיצוב סכמת מסד נתונים המותאמת לקטלוג שלכם ולתדירות הייבוא
- מימוש לוגר עם חציצה, diff ודוח
- הגדרת התראות (אימייל, טלגרם לפי בקשה)
- ממשק צפייה בלוגים בפאנל הניהול (טבלה עם סינון לפי ייבוא, סטטוס, SKU)
- סיבוב לוגים ישנים (פקודת Artisan, תקופת שמירה ניתנת להגדרה)
- תיעוד API ומדריך למנהל
- אחריות על קוד — 6 חודשים
לוח זמנים לביצוע
| שלב | משך |
|---|---|
| ImportLogger עם חציצה, טבלאות DB, מוני סיום | יום אחד |
| diff שדות, דוח מצטבר, התראות | 0.5 יום |
| ממשק צפייה בלוגים בפאנל הניהול, סיבוב רשומות ישנות | 0.5 יום |
Logging — גישה סטנדרטית למעקב אחר אירועים במערכות IT.
צרו קשר לייעוץ. הזמינו היום את מימוש מערכת הלוגים לייבוא. קבלו שקיפות מלאה בייבוא והפחתת הפסדים.







