Sunting: Karena tampaknya tidak ada solusi, atau saya melakukan sesuatu yang sangat tidak standar sehingga tidak ada yang tahu - Saya akan merevisi pertanyaan saya untuk juga bertanya: Apa cara terbaik untuk menyelesaikan pencatatan ketika aplikasi python membuat banyak panggilan sistem?
Aplikasi saya memiliki dua mode. Dalam mode interaktif, saya ingin semua output masuk ke layar serta ke file log, termasuk output dari panggilan sistem apa pun. Dalam mode daemon, semua output masuk ke log. Mode daemon bekerja sangat baik menggunakan os.dup2()
. Saya tidak dapat menemukan cara untuk "tee" semua output ke log dalam mode interaktif, tanpa memodifikasi setiap panggilan sistem.
Dengan kata lain, saya ingin fungsionalitas dari baris perintah 'tee' untuk setiap output yang dihasilkan oleh aplikasi python, termasuk output panggilan sistem .
Untuk memperjelas:
Untuk mengarahkan ulang semua output, saya melakukan sesuatu seperti ini, dan itu berfungsi dengan baik:
# open our log file
so = se = open("%s.log" % self.name, 'w', 0)
# re-open stdout without buffering
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
# redirect stdout and stderr to the log file opened above
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
Yang menyenangkan tentang ini adalah tidak memerlukan panggilan cetak khusus dari sisa kode. Kode ini juga menjalankan beberapa perintah shell, jadi senang tidak harus berurusan dengan masing-masing output mereka secara terpisah juga.
Sederhananya, saya ingin melakukan hal yang sama, kecuali menduplikasi alih-alih mengarahkan.
Mula-mula saya berpikir, hanya membalikkan saja dup2
seharusnya berhasil. Kenapa tidak? Inilah tes saya:
import os, sys
### my broken solution:
so = se = open("a.log", 'w', 0)
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
os.dup2(sys.stdout.fileno(), so.fileno())
os.dup2(sys.stderr.fileno(), se.fileno())
###
print("foo bar")
os.spawnve("P_WAIT", "/bin/ls", ["/bin/ls"], {})
os.execve("/bin/ls", ["/bin/ls"], os.environ)
File "a.log" harus identik dengan apa yang ditampilkan di layar.
Jawaban:
Karena Anda merasa nyaman memunculkan proses eksternal dari kode Anda, Anda bisa menggunakannya
tee
sendiri. Saya tidak tahu adanya panggilan sistem Unix yang melakukan persis apa yangtee
dilakukannya.Anda juga bisa meniru
tee
menggunakan paket multiprosesing (atau menggunakan pemrosesan jika Anda menggunakan Python 2.5 atau yang lebih lama).Memperbarui
Berikut ini adalah versi yang kompatibel dengan Python 3.3 +:
sumber
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
tidak lagi berfungsi sejak python 3.3 (lihat PEP 3116)tee.stdin.close()
di akhir program saya. Saya juga mendapatkan "ResourceWarning: subprocess 1842 masih berjalan", dan menambahkansys.stdout.close(); sys.stderr.close()
pada akhir program memperbaikinya.Saya pernah mengalami masalah yang sama sebelumnya dan merasa potongan ini sangat berguna:
dari: http://mail.python.org/pipermail/python-list/2007-May/438106.html
sumber
__del__
tidak dipanggil hingga akhir eksekusi. Lihat stackoverflow.com/questions/6104535/…The
print
pernyataan akan memanggilwrite()
metode dari setiap objek yang Anda tetapkan untuk sys.stdout.Saya akan memutar kelas kecil untuk menulis ke dua tempat sekaligus ...
Sekarang
print
pernyataan akan bergema ke layar dan menambahkan file log Anda:Ini jelas cepat dan kotor. Beberapa catatan:
<stdout>
jika Anda tidak akan masuk selama durasi program.Ini semua cukup mudah sehingga saya nyaman meninggalkannya sebagai latihan untuk pembaca. Wawasan utama di sini adalah bahwa
print
hanya memanggil "objek seperti file" yang ditugaskansys.stdout
.sumber
The print statement will call the write() method of any object you assign to sys.stdout
. Dan bagaimana dengan fungsi lain mengirim data ke stdout tidak menggunakanprint
. Sebagai contoh, jika saya membuat proses menggunakansubprocess.call
outputnya pergi ke konsol tetapi tidak kelog.dat
file ... apakah ada cara untuk memperbaikinya?Yang benar-benar Anda inginkan adalah
logging
modul dari pustaka standar. Buat logger dan lampirkan dua penangan, satu akan menulis ke file dan yang lainnya ke stdout atau stderr.Lihat Masuk ke beberapa tujuan untuk detailnya
sumber
logging
modul tidak akan mengalihkan output dari panggilan sistem sepertios.write(1, b'stdout')
Berikut ini adalah solusi lain, yang lebih umum daripada yang lain - ini mendukung pemisahan output (ditulis untuk
sys.stdout
) ke sejumlah objek seperti file. Tidak ada persyaratan yang__stdout__
disertakan.CATATAN: Ini adalah bukti konsep. Implementasi di sini tidak lengkap, karena hanya membungkus metode objek seperti file (mis
write
), meninggalkan anggota / properti / setattr, dll. Namun, itu mungkin cukup baik bagi kebanyakan orang seperti saat ini berdiri.Apa yang saya suka tentang hal itu, selain umum, adalah bahwa itu bersih dalam arti tidak membuat panggilan langsung ke
write
,flush
,os.dup2
, dllsumber
_wrap
di sini? Tidak bisakah Anda menyalin kode di sana ke dalam__getattr__
dan itu bekerja sama?multifile([])
membuat file yang membangkitkanUnboundLocalError
setiap kali Anda memanggil salah satu metodenya. (res
dikembalikan tanpa ditugaskan)Seperti dijelaskan di tempat lain, mungkin solusi terbaik adalah dengan menggunakan modul logging secara langsung:
Namun, ada beberapa (jarang) kesempatan di mana Anda benar-benar menginginkannya mengarahkan ulang stdout. Saya mengalami situasi ini ketika saya memperluas perintah runserver Django yang menggunakan print: Saya tidak ingin meretas sumber Django tetapi membutuhkan pernyataan cetak untuk pergi ke file.
Ini adalah cara untuk mengarahkan stdout dan stderr menjauh dari shell menggunakan modul logging:
Anda hanya harus menggunakan implementasi LogFile ini jika Anda benar-benar tidak dapat menggunakan modul logging secara langsung.
sumber
Saya menulis
tee()
implementasi dengan Python yang seharusnya bisa digunakan pada kebanyakan kasus, dan ini juga bisa digunakan di Windows.https://github.com/pycontribs/tendo
Juga, Anda dapat menggunakannya dalam kombinasi dengan
logging
modul dari Python jika Anda mau.sumber
(Ah, baca kembali pertanyaan Anda dan lihat ini tidak berlaku.)
Berikut adalah contoh program yang menggunakan modul python logging . Modul logging ini telah ada di semua versi sejak 2.3. Dalam sampel ini logging dapat dikonfigurasi oleh opsi baris perintah.
Dalam mode cukup itu hanya akan masuk ke file, dalam mode normal itu akan masuk ke file dan konsol.
sumber
Untuk melengkapi jawaban John T: https://stackoverflow.com/a/616686/395687
Saya menambahkan
__enter__
dan__exit__
metode untuk menggunakannya sebagai manajer konteks denganwith
kata kunci, yang memberikan kode iniIni kemudian dapat digunakan sebagai
sumber
__del__
fungsionalitas ke__exit__
__del__
adalah ide yang buruk. Itu harus dipindahkan ke fungsi 'tutup' yang dipanggil__exit__
.Saya tahu pertanyaan ini telah dijawab berulang kali, tetapi untuk ini saya telah mengambil jawaban utama dari jawaban John T dan memodifikasinya sehingga berisi flush yang disarankan dan mengikuti versi revisi yang ditautkan. Saya juga menambahkan masuk dan keluar seperti yang disebutkan dalam cladmi's jawaban untuk digunakan dengan pernyataan with. Selain itu, dokumentasi menyebutkan untuk menggunakan file flush
os.fsync()
jadi saya telah menambahkannya juga. Saya tidak tahu apakah Anda benar - benar membutuhkannya tetapi ada di sana.Anda kemudian dapat menggunakannya
atau
sumber
mode="ab"
dan dalamwrite
fungsiself.file.write(message.encode("utf-8"))
solusi lain menggunakan modul logging:
sumber
Tidak ada jawaban di atas yang benar-benar menjawab masalah yang diajukan. Saya tahu ini adalah utas lama, tetapi saya pikir masalah ini jauh lebih sederhana daripada yang dilakukan semua orang:
Sekarang ini akan mengulang semuanya ke handler sys.stderr normal dan file Anda. Buat kelas lain
tee_out
untuksys.stdout
.sumber
tee=tee_err();tee.write('');tee.write('');...
membuka + menutup file untuk masing-masingwrite
. Lihat stackoverflow.com/q/4867468 dan stackoverflow.com/q/164053 untuk argumen yang menentang praktik ini.Sesuai permintaan oleh @ user5359531 dalam komentar di bawah jawaban @John T , inilah salinan pos yang direferensikan ke versi revisi dari diskusi terkait dalam jawaban itu:
sumber
Saya sedang menulis skrip untuk menjalankan skrip cmd-line. (Karena dalam beberapa kasus, tidak ada pengganti yang layak untuk perintah Linux - seperti kasus rsync.)
Apa yang saya benar-benar inginkan adalah menggunakan mekanisme logging python default dalam setiap kasus di mana dimungkinkan untuk melakukannya, tetapi untuk tetap menangkap kesalahan ketika ada sesuatu yang salah yang tidak terduga.
Kode ini sepertinya melakukan trik. Ini mungkin tidak terlalu elegan atau efisien (meskipun tidak menggunakan string + = string, jadi setidaknya tidak memiliki potensi leher botol tertentu). Saya mempostingnya kalau-kalau itu memberi orang lain ide yang berguna.
Jelas, jika Anda tidak tunduk pada imajinasi seperti saya, ganti LOG_IDENTIFIER dengan string lain yang Anda tidak ingin pernah melihat seseorang menulis ke log.
sumber
Jika Anda ingin mencatat semua output ke file DAN output ke file teks maka Anda dapat melakukan hal berikut. Agak gila tapi berhasil:
EDIT: Perhatikan bahwa ini tidak mencatat kesalahan kecuali Anda mengarahkan ulang sys.stderr ke sys.stdout
EDIT2: Masalah kedua adalah bahwa Anda harus melewati 1 argumen tidak seperti dengan fungsi builtin.
EDIT3: Lihat kode sebelum menulis stdin dan stdout ke konsol dan file dengan stderr hanya pergi ke file
sumber
Aku menulis pengganti penuh untuk
sys.stderr
dan hanya digandakan kode mengubah namastderr
untukstdout
untuk membuatnya juga tersedia untuk menggantikansys.stdout
.Untuk melakukan ini, saya membuat jenis objek yang sama dengan saat ini
stderr
danstdout
, dan meneruskan semua metode ke sistem aslistderr
danstdout
:Untuk menggunakan ini, Anda bisa memanggil
StdErrReplament::lock(logger)
danStdOutReplament::lock(logger)
melewati logger yang ingin Anda gunakan untuk mengirim teks output. Sebagai contoh:Menjalankan kode ini, Anda akan melihat di layar:
Dan pada isi file:
Jika Anda juga ingin melihat konten
log.debug
panggilan di layar, Anda perlu menambahkan stream handler ke logger Anda. Dalam hal ini akan menjadi seperti ini:Yang akan ditampilkan seperti ini saat menjalankan:
Meskipun masih menyimpan ini ke file
my_log_file.txt
:Saat menonaktifkan ini dengan
StdErrReplament:unlock()
, itu hanya akan mengembalikan perilaku standarstderr
aliran, karena logger terlampir tidak dapat dilepaskan karena orang lain dapat memiliki referensi ke versi yang lebih lama. Inilah sebabnya mengapa itu adalah singleton global yang tidak pernah bisa mati. Oleh karena itu, dalam kasus memuat kembali modul ini denganimp
atau sesuatu yang lain, itu tidak akan pernah merebut kembali arussys.stderr
karena sudah disuntikkan padanya dan disimpan secara internal.sumber