Logging
يوفر الوحدة Logging
وسيلة لتسجيل تاريخ وتقدم عملية حسابية كسجل للأحداث. يتم إنشاء الأحداث عن طريق إدراج عبارة تسجيل في الشيفرة المصدرية، على سبيل المثال:
@warn "Abandon printf debugging, all ye who enter here!"
┌ Warning: Abandon printf debugging, all ye who enter here!
└ @ Main REPL[1]:1
يوفر النظام عدة مزايا على إضافة استدعاءات لـ println()
في شفرة المصدر الخاصة بك. أولاً، يتيح لك التحكم في رؤية الرسائل وعرضها دون الحاجة إلى تعديل شفرة المصدر. على سبيل المثال، على عكس @warn
أعلاه
@debug "The sum of some values $(sum(rand(100)))"
لن ينتج أي مخرجات بشكل افتراضي. علاوة على ذلك، من الرخيص جدًا ترك بيانات التصحيح مثل هذه في الشيفرة المصدرية لأن النظام يتجنب تقييم الرسالة إذا كان من المقرر تجاهلها لاحقًا. في هذه الحالة، لن يتم تنفيذ sum(rand(100))
ومعالجة السلسلة المرتبطة بها أبدًا ما لم يتم تمكين تسجيل الأخطاء.
ثانيًا، تتيح لك أدوات التسجيل إرفاق بيانات عشوائية بكل حدث كمجموعة من أزواج المفتاح–القيمة. وهذا يسمح لك بالتقاط المتغيرات المحلية وحالة البرنامج الأخرى للتحليل لاحقًا. على سبيل المثال، لإرفاق المتغير المحلي للمصفوفة A
ومجموع المتجه v
كمفتاح s
يمكنك استخدام
A = ones(Int, 4, 4)
v = ones(100)
@info "Some variables" A s=sum(v)
# output
┌ Info: Some variables
│ A =
│ 4×4 Matrix{Int64}:
│ 1 1 1 1
│ 1 1 1 1
│ 1 1 1 1
│ 1 1 1 1
└ s = 100.0
تشارك جميع ماكروهات التسجيل @debug
و @info
و @warn
و @error
ميزات شائعة يتم وصفها بالتفصيل في الوثائق الخاصة بالماكرو الأكثر عمومية @logmsg
.
Log event structure
كل حدث يولد عدة قطع من البيانات، بعضها مقدمة من المستخدم وبعضها مستخرج تلقائيًا. دعونا نفحص البيانات المحددة من قبل المستخدم أولاً:
مستوى السجل هو فئة واسعة للرسالة التي تُستخدم للتصفية المبكرة. هناك عدة مستويات قياسية من النوع
LogLevel
؛ كما أن المستويات المعرفة من قبل المستخدم ممكنة أيضًا. كل منها مميز في الغرض:Logging.Debug
(مستوى السجل -1000) هو معلومات موجهة لمطور البرنامج. هذه الأحداث معطلة بشكل افتراضي.Logging.Info
(مستوى السجل 0) هو للمعلومات العامة للمستخدم. اعتبره بديلاً لاستخدامprintln
مباشرة.Logging.Warn
(مستوى السجل 1000) يعني أن هناك شيئًا خاطئًا ومن المحتمل أن تكون هناك حاجة إلى إجراء، ولكن حتى الآن لا يزال البرنامج يعمل.Logging.Error
(مستوى السجل 2000) يعني أن هناك شيئًا خاطئًا ومن غير المحتمل أن يتم استرداده، على الأقل من خلال هذا الجزء من الشيفرة. غالبًا ما يكون هذا المستوى من السجل غير ضروري حيث يمكن أن ينقل رمي استثناء جميع المعلومات المطلوبة.
الـ رسالة هي كائن يصف الحدث. وفقًا للتقاليد، يُفترض أن تكون
AbstractString
s المرسلة كرسائل بتنسيق ماركداون. سيتم عرض الأنواع الأخرى باستخدامprint(io, obj)
أوstring(obj)
للإخراج النصي، وربماshow(io,mime,obj)
لعرض الوسائط المتعددة الأخرى المستخدمة في المسجل المثبت.تسمح أزواج المفتاح–القيمة الاختيارية بإرفاق بيانات عشوائية بكل حدث. بعض المفاتيح لها معاني تقليدية يمكن أن تؤثر على كيفية تفسير الحدث (انظر
@logmsg
).
ينشئ النظام أيضًا بعض المعلومات القياسية لكل حدث:
- الوحدة التي تم فيها توسيع ماكرو السجل.
ملف
وسطر
حيث يحدث ماكرو التسجيل في شفرة المصدر.- معرف
id
هو معرف فريد وثابت ل عبارة كود المصدر حيث يظهر ماكرو التسجيل. تم تصميم هذا المعرف ليكون مستقرًا إلى حد ما حتى لو تغير كود المصدر في الملف، طالما أن عبارة التسجيل نفسها تظل كما هي. - مجموعة
group
للحدث، والتي يتم تعيينها إلى الاسم الأساسي للملف بشكل افتراضي، بدون امتداد. يمكن استخدام ذلك لتجميع الرسائل في فئات بشكل أكثر دقة من مستوى السجل (على سبيل المثال، جميع تحذيرات الإهمال لها مجموعة:depwarn
)، أو في تجميعات منطقية عبر أو داخل الوحدات.
لاحظ أن بعض المعلومات المفيدة مثل وقت الحدث غير مدرجة بشكل افتراضي. وذلك لأن مثل هذه المعلومات قد تكون مكلفة للاستخراج وهي أيضًا متاحة ديناميكيًا للمسجل الحالي. من السهل تعريف custom logger لتعزيز بيانات الحدث بالوقت، وتتبع الأخطاء، وقيم المتغيرات العالمية وغيرها من المعلومات المفيدة حسب الحاجة.
Processing log events
كما ترى في الأمثلة، فإن عبارات السجل لا تذكر أين تذهب أحداث السجل أو كيف تتم معالجتها. هذه ميزة تصميم رئيسية تجعل النظام قابلاً للتكوين وطبيعيًا للاستخدام المتزامن. يقوم بذلك من خلال فصل قضيتين مختلفتين:
- إنشاء أحداث السجل هو من اهتمامات مؤلف الوحدة الذي يحتاج إلى تحديد مكان حدوث الأحداث والمعلومات التي يجب تضمينها.
- معالجة أحداث السجل — أي العرض، والتصفية، والتجميع، والتسجيل — هي من اهتمامات مؤلف التطبيق الذي يحتاج إلى دمج عدة وحدات معًا في تطبيق متعاون.
Loggers
يتم تنفيذ معالجة الأحداث بواسطة مسجل، وهو أول قطعة من الشيفرة القابلة للتكوين من قبل المستخدم لرؤية الحدث. يجب أن تكون جميع المسجلات من الأنواع الفرعية لـ AbstractLogger
.
عند حدوث حدث، يتم العثور على السجل المناسب من خلال البحث عن سجل محلي للمهام مع سجل عالمي كخيار احتياطي. الفكرة هنا هي أن كود التطبيق يعرف كيف يجب معالجة أحداث السجل ويكون موجودًا في مكان ما في أعلى مكدس الاستدعاء. لذا يجب علينا البحث لأعلى من خلال مكدس الاستدعاء لاكتشاف السجل — أي أن السجل يجب أن يكون محددًا ديناميكيًا. (هذه نقطة تباين مع أطر السجل حيث يكون السجل محددًا نحويًا؛ يتم توفيره بشكل صريح من قبل مؤلف الوحدة أو كمتغير عالمي بسيط. في مثل هذا النظام، يكون من المحرج التحكم في السجل أثناء تجميع الوظائف من وحدات متعددة.)
يمكن تعيين السجل العالمي باستخدام global_logger
، ويتم التحكم في سجلات المهام المحلية باستخدام with_logger
. المهام التي تم إنشاؤها حديثًا ترث السجل من المهمة الأصلية.
يوجد ثلاثة أنواع من المسجلات مقدمة من المكتبة. ConsoleLogger
هو المسجل الافتراضي الذي تراه عند بدء REPL. يعرض الأحداث بتنسيق نصي قابل للقراءة ويحاول تقديم تحكم بسيط ولكن سهل الاستخدام في التنسيق والتصفية. NullLogger
هو وسيلة مريحة للتخلص من جميع الرسائل عند الضرورة؛ إنه المعادل للتسجيل لـ devnull
التدفق. SimpleLogger
هو مسجل بتنسيق نصي بسيط للغاية، مفيد بشكل أساسي لتصحيح نظام التسجيل نفسه.
يجب أن تأتي سجلات مخصصة مع تحميلات للوظائف الموصوفة في reference section.
Early filtering and message handling
عند حدوث حدث، تحدث بعض خطوات التصفية المبكرة لتجنب توليد رسائل سيتمdiscardها:
- يتم التحقق من مستوى سجل الرسائل مقابل مستوى الحد الأدنى العالمي (المحدد عبر
disable_logging
). هذه إعداد عالمي بدائي ولكنه رخيص للغاية. - يتم البحث عن حالة السجل الحالية والتحقق من مستوى الرسالة مقابل الحد الأدنى المخزن مؤقتًا للسجل، كما هو موجود من خلال استدعاء
Logging.min_enabled_level
. يمكن تجاوز هذا السلوك عبر متغيرات البيئة (المزيد عن ذلك لاحقًا). - تتم استدعاء دالة
Logging.shouldlog
مع السجل الحالي، مع أخذ بعض المعلومات الأساسية (المستوى، الوحدة، المجموعة، المعرف) التي يمكن حسابها بشكل ثابت. بشكل أكثر فائدة، يتم تمريرshouldlog
حدثid
الذي يمكن استخدامه للتخلص من الأحداث مبكرًا بناءً على دالة شرطية مخزنة.
إذا اجتاز كل هذه الفحوصات، يتم تقييم الرسالة وأزواج المفتاح–القيمة بالكامل وتمريرها إلى المسجل الحالي عبر دالة Logging.handle_message
. قد تقوم handle_message()
بإجراء تصفية إضافية حسب الحاجة وعرض الحدث على الشاشة، أو حفظه في ملف، إلخ.
يتم التقاط وتسجيل الاستثناءات التي تحدث أثناء إنشاء حدث السجل بشكل افتراضي. يمنع ذلك الأحداث المعطلة الفردية من تعطل التطبيق، وهو مفيد عند تمكين أحداث التصحيح القليلة الاستخدام في نظام الإنتاج. يمكن تخصيص هذا السلوك لكل نوع من أنواع المسجلين عن طريق تمديد Logging.catch_exceptions
.
Testing log events
تعتبر أحداث السجل نتيجة جانبية لتشغيل الكود العادي، ولكن قد تجد نفسك ترغب في اختبار رسائل معلوماتية وتحذيرات معينة. يوفر لك وحدة Test
ماكرو @test_logs
الذي يمكن استخدامه لمطابقة الأنماط ضد تدفق أحداث السجل.
Environment variables
يمكن أن يتأثر تصفية الرسائل من خلال متغير البيئة JULIA_DEBUG
، ويعمل كطريقة سهلة لتمكين تسجيل الأخطاء لملف أو وحدة. تحميل جوليا مع JULIA_DEBUG=loading
سيفعل رسائل سجل @debug
في loading.jl
. على سبيل المثال، في قذائف لينكس:
$ JULIA_DEBUG=loading julia -e 'using OhMyREPL'
┌ Debug: Rejecting cache file /home/user/.julia/compiled/v0.7/OhMyREPL.ji due to it containing an incompatible cache header
└ @ Base loading.jl:1328
[ Info: Recompiling stale cache file /home/user/.julia/compiled/v0.7/OhMyREPL.ji for module OhMyREPL
┌ Debug: Rejecting cache file /home/user/.julia/compiled/v0.7/Tokenize.ji due to it containing an incompatible cache header
└ @ Base loading.jl:1328
...
على ويندوز، يمكن تحقيق نفس الشيء في CMD
عن طريق تشغيل set JULIA_DEBUG="loading"
أولاً وفي Powershell
عبر $env:JULIA_DEBUG="loading"
.
بالمثل، يمكن استخدام متغير البيئة لتمكين تسجيل الأخطاء لوحدات معينة، مثل Pkg
، أو جذور الوحدات (انظر Base.moduleroot
). لتمكين جميع تسجيلات الأخطاء، استخدم القيمة الخاصة all
.
لتفعيل تسجيل الأخطاء من REPL، قم بتعيين ENV["JULIA_DEBUG"]
إلى اسم الوحدة المعنية. الوظائف المعرفة في REPL تنتمي إلى الوحدة Main
؛ يمكن تفعيل التسجيل لها بهذه الطريقة:
julia> foo() = @debug "foo"
foo (generic function with 1 method)
julia> foo()
julia> ENV["JULIA_DEBUG"] = Main
Main
julia> foo()
┌ Debug: foo
└ @ Main REPL[1]:1
استخدم فاصلة لفصل لتمكين التصحيح لعدة وحدات: JULIA_DEBUG=loading,Main
.
Examples
Example: Writing log events to a file
أحيانًا قد يكون من المفيد كتابة أحداث السجل إلى ملف. إليك مثال على كيفية استخدام مسجل محلي للمهمة ومسجل عالمي لكتابة المعلومات إلى ملف نصي:
# Load the logging module
julia> using Logging
# Open a textfile for writing
julia> io = open("log.txt", "w+")
IOStream(<file log.txt>)
# Create a simple logger
julia> logger = SimpleLogger(io)
SimpleLogger(IOStream(<file log.txt>), Info, Dict{Any,Int64}())
# Log a task-specific message
julia> with_logger(logger) do
@info("a context specific log message")
end
# Write all buffered messages to the file
julia> flush(io)
# Set the global logger to logger
julia> global_logger(logger)
SimpleLogger(IOStream(<file log.txt>), Info, Dict{Any,Int64}())
# This message will now also be written to the file
julia> @info("a global log message")
# Close the file
julia> close(io)
Example: Enable debug-level messages
هنا مثال على إنشاء ConsoleLogger
الذي يسمح بمرور أي رسائل بمستوى سجل أعلى من، أو يساوي، Logging.Debug
.
julia> using Logging
# Create a ConsoleLogger that prints any log messages with level >= Debug to stderr
julia> debuglogger = ConsoleLogger(stderr, Logging.Debug)
# Enable debuglogger for a task
julia> with_logger(debuglogger) do
@debug "a context specific log message"
end
# Set the global logger
julia> global_logger(debuglogger)
Reference
Logging module
Logging.Logging
— Moduleأدوات لالتقاط وتصفية وعرض تدفقات أحداث السجل. عادةً لا تحتاج إلى استيراد Logging
لإنشاء أحداث السجل؛ لهذا فإن الماكرو القياسي للتسجيل مثل @info
تم تصديره بالفعل بواسطة Base
ومتاحة بشكل افتراضي.
Creating events
Logging.@logmsg
— Macro@debug رسالة [مفتاح=قيمة | قيمة ...]
@info رسالة [مفتاح=قيمة | قيمة ...]
@warn رسالة [مفتاح=قيمة | قيمة ...]
@error رسالة [مفتاح=قيمة | قيمة ...]
@logmsg مستوى رسالة [مفتاح=قيمة | قيمة ...]
قم بإنشاء سجل تسجيل مع `رسالة` معلوماتية. لراحة المستخدم، تم تعريف أربعة ماكرو للتسجيل `@debug`، `@info`، `@warn` و `@error` والتي تسجل عند مستويات الشدة القياسية `Debug`، `Info`، `Warn` و `Error`. يسمح `@logmsg` بتعيين `مستوى` برمجياً إلى أي `LogLevel` أو أنواع مستويات تسجيل مخصصة.
يجب أن تكون `رسالة` تعبيرًا يتم تقييمه إلى سلسلة نصية تكون وصفًا قابلًا للقراءة البشرية لحدث التسجيل. وفقًا للتقاليد، سيتم تنسيق هذه السلسلة كـ markdown عند تقديمها.
تدعم القائمة الاختيارية من أزواج `مفتاح=قيمة` بيانات تعريف المستخدم العشوائية التي سيتم تمريرها إلى واجهة تسجيل السجلات كجزء من سجل التسجيل. إذا تم تقديم تعبير `قيمة` فقط، سيتم إنشاء مفتاح يمثل التعبير باستخدام [`Symbol`](@ref). على سبيل المثال، `x` تصبح `x=x`، و `foo(10)` تصبح `Symbol("foo(10)")=foo(10)`. لتفكيك قائمة من أزواج المفتاح والقيمة، استخدم بناء الجملة العادي للتفكيك، `@info "blah" kws...`.
هناك بعض المفاتيح التي تسمح بتجاوز بيانات السجل المولدة تلقائيًا:
* `_module=mod` يمكن استخدامها لتحديد وحدة منشأ مختلفة من موقع مصدر الرسالة.
* `_group=symbol` يمكن استخدامها لتجاوز مجموعة الرسالة (عادة ما يتم اشتقاقها من الاسم الأساسي لملف المصدر).
* `_id=symbol` يمكن استخدامها لتجاوز معرف الرسالة الفريد المولد تلقائيًا. هذا مفيد إذا كنت بحاجة إلى ربط الرسائل المولدة عن كثب على خطوط مصدر مختلفة.
* `_file=string` و `_line=integer` يمكن استخدامها لتجاوز موقع المصدر الظاهر لرسالة سجل.
هناك أيضًا بعض أزواج المفتاح والقيمة التي لها معنى تقليدي:
* `maxlog=integer` يجب استخدامها كإشارة إلى الواجهة الخلفية بأن الرسالة يجب أن تُعرض لا أكثر من `maxlog` مرات.
* `exception=ex` يجب استخدامها لنقل استثناء مع رسالة سجل، وغالبًا ما تستخدم مع `@error`. يمكن إرفاق تتبع مرتبط `bt` باستخدام الزوج `exception=(ex,bt)`.
# أمثلة
julia @debug "معلومات تصحيح مفصلة. غير مرئية بشكل افتراضي" @info "رسالة معلوماتية" @warn "كان هناك شيء غريب. يجب أن تولي اهتمامًا" @error "حدث خطأ غير قاتل"
x = 10 @info "بعض المتغيرات مرتبطة بالرسالة" x a=42.0
@debug begin sA = sum(A) "sum(A) = :sA عملية مكلفة، يتم تقييمها فقط عندما تعود shouldlog
بـ true" end
for i=1:10000 @info "مع الواجهة الخلفية الافتراضية، سترى فقط (i = :i) عشر مرات" maxlog=10 @debug "Algorithm1" i progress=i/10000 end ```
Logging.LogLevel
— TypeLogLevel(level)
شدة/وضوح سجل السجل.
يوفر مستوى السجل مفتاحًا يمكن من خلاله تصفية سجلات السجل المحتملة، قبل القيام بأي عمل آخر لبناء بنية بيانات سجل السجل نفسها.
أمثلة
julia> Logging.LogLevel(0) == Logging.Info
true
Logging.Debug
— Constantتصحيح
اسم مستعار لـ LogLevel(-1000)
.
Logging.Info
— Constantمعلومات
اختصار لـ LogLevel(0)
.
Logging.Warn
— Constantتحذير
مرادف لـ LogLevel(1000)
.
Logging.Error
— Constantخطأ
اسم مستعار لـ LogLevel(2000)
.
Logging.BelowMinLevel
— ConstantBelowMinLevel
اسم مستعار لـ LogLevel(-1_000_001)
.
Logging.AboveMaxLevel
— ConstantAboveMaxLevel
اسم مستعار لـ LogLevel(1_000_001)
.
Processing events with AbstractLogger
يتم التحكم في معالجة الأحداث من خلال تجاوز الوظائف المرتبطة بـ AbstractLogger
:
Methods to implement | Brief description | |
---|---|---|
Logging.handle_message | Handle a log event | |
Logging.shouldlog | Early filtering of events | |
Logging.min_enabled_level | Lower bound for log level of accepted events | |
Optional methods | Default definition | Brief description |
Logging.catch_exceptions | true | Catch exceptions during event evaluation |
Logging.AbstractLogger
— Typeيتحكم المسجل في كيفية تصفية سجلات السجل وإرسالها. عندما يتم إنشاء سجل، يكون المسجل هو أول قطعة من الشيفرة القابلة للتكوين من قبل المستخدم التي تقوم بفحص السجل وتقرر ماذا تفعل به.
Logging.handle_message
— Functionhandle_message(logger, level, message, _module, group, id, file, line; key1=val1, ...)
سجل رسالة إلى logger
عند level
. الموقع المنطقي الذي تم فيه إنشاء الرسالة يتم تحديده بواسطة الوحدة _module
و group
؛ وموقع المصدر بواسطة file
و line
. id
هو قيمة فريدة تعسفية (عادةً ما تكون Symbol
) تُستخدم كمفتاح لتحديد عبارة السجل عند التصفية.
Logging.shouldlog
— Functionshouldlog(logger, level, _module, group, id)
ارجع true
عندما يقبل logger
رسالة عند level
، تم إنشاؤها لـ _module
، group
ومع معرف سجل فريد id
.
Logging.min_enabled_level
— Functionmin_enabled_level(logger)
إرجاع الحد الأدنى لمستوى التمكين لـ logger
للتصفية المبكرة. أي أن مستوى السجل الذي يكون أقل أو يساوي جميع الرسائل التي يتم تصفيتها.
Logging.catch_exceptions
— Functioncatch_exceptions(logger)
ارجع true
إذا كان يجب على السجل التقاط الاستثناءات التي تحدث أثناء إنشاء سجل السجل. بشكل افتراضي، يتم التقاط الرسائل
بشكل افتراضي، يتم التقاط جميع الاستثناءات لمنع توليد رسائل السجل من تعطل البرنامج. هذا يسمح للمستخدمين بتبديل الوظائف القليلة الاستخدام - مثل تسجيل الأخطاء - في نظام الإنتاج بثقة.
إذا كنت ترغب في استخدام التسجيل كمسار تدقيق، يجب عليك تعطيل ذلك لنوع السجل الخاص بك.
Logging.disable_logging
— Functiondisable_logging(level)
تعطيل جميع رسائل السجل عند مستويات السجل التي تساوي أو تقل عن level
. هذه إعداد عالمي، يهدف إلى جعل تسجيل الأخطاء رخيصًا للغاية عند تعطيله.
أمثلة
Logging.disable_logging(Logging.Info) # تعطيل الأخطاء والمعلومات
Using Loggers
تثبيت الفاحص والتفتيش:
Logging.global_logger
— Functionglobal_logger()
إرجاع السجل العالمي، المستخدم لاستقبال الرسائل عندما لا يوجد سجل محدد للمهمة الحالية.
global_logger(logger)
تعيين السجل العالمي إلى logger
، وإرجاع السجل العالمي السابق.
Logging.with_logger
— Functionwith_logger(function, logger)
نفذ function
، موجهًا جميع رسائل السجل إلى logger
.
أمثلة
function test(x)
@info "x = $x"
end
with_logger(logger) do
test(1)
test([1,2])
end
Logging.current_logger
— Functioncurrent_logger()
إرجاع السجل للمهام الحالية، أو السجل العالمي إذا لم يكن هناك سجل مرتبط بالمهام.
سجلات يتم تزويدها مع النظام:
Logging.NullLogger
— TypeNullLogger()
مسجل يقوم بتعطيل جميع الرسائل ولا ينتج أي مخرجات - المعادل المسجّل لـ /dev/null.
Base.CoreLogging.ConsoleLogger
— TypeConsoleLogger([stream,] min_level=Info; meta_formatter=default_metafmt,
show_limited=true, right_justify=0)
مسجل مع تنسيق محسّن للقراءة في وحدة التحكم النصية، على سبيل المثال العمل التفاعلي مع REPL جوليا.
يتم تصفية مستويات السجل التي تقل عن min_level
.
يمكن التحكم في تنسيق الرسائل عن طريق تعيين وسائط الكلمات الرئيسية:
meta_formatter
هي دالة تأخذ بيانات تعريف حدث السجل(level, _module, group, id, file, line)
وتعيد لونًا (كما سيتم تمريره إلى printstyled)، وبادئة ولاحقة لرسالة السجل. الافتراضي هو أن يتم البادئة بمستوى السجل ولاحقة تحتوي على الوحدة، والملف وموقع السطر.show_limited
يحد من طباعة الهياكل البيانية الكبيرة إلى شيء يمكن أن يتناسب مع الشاشة عن طريق تعيين مفتاح:limit
فيIOContext
أثناء التنسيق.right_justify
هو العمود الصحيح الذي يتم فيه تبرير بيانات تعريف السجل. الافتراضي هو صفر (تذهب بيانات التعريف إلى سطرها الخاص).
Logging.SimpleLogger
— TypeSimpleLogger([stream,] min_level=Info)
مسجل بسيط لتسجيل جميع الرسائل بمستوى أكبر من أو يساوي min_level
إلى stream
. إذا كان stream
مغلقًا، فسيتم تسجيل الرسائل بمستوى سجل أكبر أو يساوي Warn
إلى stderr
وما دونه إلى stdout
.