Edycja: Ponieważ wydaje się, że albo nie ma rozwiązania, albo robię coś tak niestandardowego, że nikt nie wie - zrewiduję moje pytanie, aby zapytać również: Jaki jest najlepszy sposób na wykonanie logowania, gdy aplikacja Pythona tworzy dużo wywołań systemowych?
Moja aplikacja ma dwa tryby. W trybie interaktywnym chcę, aby wszystkie dane wyjściowe były wyświetlane na ekranie, a także do pliku dziennika, w tym danych wyjściowych z wszelkich wywołań systemowych. W trybie demona wszystkie dane wyjściowe trafiają do dziennika. Tryb demona działa świetnie przy użyciu os.dup2()
. Nie mogę znaleźć sposobu, aby wszystkie dane wyjściowe były przesyłane do dziennika w trybie interaktywnym bez modyfikowania każdego wywołania systemowego.
Innymi słowy, chcę mieć funkcjonalność wiersza poleceń „tee” dla każdego wyjścia generowanego przez aplikację w języku Python, w tym danych wyjściowych wywołań systemowych .
W celu wyjaśnienia:
Aby przekierować wszystkie dane wyjściowe, robię coś takiego i działa świetnie:
# 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())
Zaletą tego jest to, że nie wymaga specjalnych wywołań print z pozostałej części kodu. Kod uruchamia również niektóre polecenia powłoki, więc miło nie jest też zajmować się każdym z ich wyników indywidualnie.
Po prostu chcę zrobić to samo, z wyjątkiem duplikowania zamiast przekierowywania.
Na początku pomyślałem, że po prostu odwrócenie instrukcji dup2
powinno działać. Dlaczego nie? Oto mój test:
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)
Plik „a.log” powinien być identyczny z tym, co zostało wyświetlone na ekranie.
Odpowiedzi:
Ponieważ czujesz się komfortowo przy tworzeniu zewnętrznych procesów z kodu, możesz użyć
tee
samego siebie. Nie znam żadnych wywołań systemowych Uniksa, które robią dokładnie to, cotee
robi.Możesz również emulować
tee
używając pakietu wieloprocesorowego (lub użyć przetwarzania, jeśli używasz Pythona 2.5 lub starszego).Aktualizacja
Oto wersja zgodna z Python 3.3 +:
źródło
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
nie działa już od Pythona 3.3 (patrz PEP 3116)tee.stdin.close()
na końcu mojego programu. Dostaję też „ResourceWarning: podproces 1842 nadal działa” i dodaniesys.stdout.close(); sys.stderr.close()
na końcu programu rozwiązuje problem.Miałem ten sam problem wcześniej i uznałem ten fragment za bardzo przydatny:
od: http://mail.python.org/pipermail/python-list/2007-May/438106.html
źródło
__del__
nie jest wywoływana do końca wykonywania. Zobacz stackoverflow.com/questions/6104535/…print
Oświadczenie będzie wywołaćwrite()
metodę dowolnego obiektu przypisanej sys.stdout.Rozkręciłbym małą klasę, żeby pisać do dwóch miejsc naraz ...
Teraz
print
instrukcja będzie zarówno echo na ekranie, jak i dołączenie do pliku dziennika:Jest to oczywiście szybkie i brudne. Kilka uwag:
<stdout>
jeśli nie będziesz logować się przez czas trwania programu.Wszystko to jest na tyle proste, że wygodnie zostawiam je jako ćwiczenia dla czytelnika. Kluczową spostrzeżeniem jest to, że
print
po prostu wywołuje przypisany do niego „obiekt podobny do pliku”sys.stdout
.źródło
The print statement will call the write() method of any object you assign to sys.stdout
. A co z innymi nieużywanymi funkcjami wysyłającymi dane na standardowe wyjścieprint
. Na przykład, jeśli utworzę proces, używającsubprocess.call
jego danych wyjściowych, trafi do konsoli, ale nie dolog.dat
pliku ... czy istnieje sposób, aby to naprawić?To, czego naprawdę potrzebujesz, to
logging
moduł z biblioteki standardowej. Utwórz rejestrator i dołącz dwa programy obsługi, jeden będzie zapisywał do pliku, a drugi do stdout lub stderr.Aby uzyskać szczegółowe informacje, zobacz Rejestrowanie w wielu miejscach docelowych
źródło
logging
moduł nie przekierowuje danych wyjściowych z wywołań systemowych, takich jakos.write(1, b'stdout')
Oto inne rozwiązanie, które jest bardziej ogólne niż inne - obsługuje podział danych wyjściowych (zapisywanych do
sys.stdout
) na dowolną liczbę obiektów podobnych do plików. Nie ma wymogu, aby__stdout__
sam był uwzględniony.UWAGA: To jest dowód słuszności koncepcji. Implementacja tutaj nie jest kompletna, ponieważ zawija tylko metody obiektów podobnych do plików (np.
write
), Pomijając elementy członkowskie / properties / setattr, itp. Jednak prawdopodobnie jest wystarczająco dobra dla większości ludzi w obecnej postaci.Co lubię o tym, inne niż jego ogólności, jest to, że jest czysty w tym sensie, że nie ma żadnych połączeń bezpośrednich do
write
,flush
,os.dup2
, itdźródło
_wrap
tu w ogóle? Nie mógłbyś skopiować tam kodu__getattr__
i działa tak samo?multifile([])
tworzy plik, który wywołujeUnboundLocalError
wywołanie jednej z jego metod. (res
jest zwracany bez przypisania)Jak opisano w innym miejscu, być może najlepszym rozwiązaniem jest bezpośrednie użycie modułu logowania:
Jednak są pewne (rzadkie) sytuacje, w których naprawdę chcesz przekierować standardowe wyjście. Miałem taką sytuację, kiedy rozszerzałem polecenie runerver django, które używa print: nie chciałem włamać się do źródła django, ale potrzebowałem instrukcji print, aby przejść do pliku.
Jest to sposób na przekierowanie stdout i stderr z dala od powłoki za pomocą modułu logującego:
Powinieneś używać tej implementacji LogFile tylko wtedy, gdy naprawdę nie możesz bezpośrednio użyć modułu logowania.
źródło
Napisałem
tee()
implementację w Pythonie, która powinna działać w większości przypadków i działa również w systemie Windows.https://github.com/pycontribs/tendo
Możesz również użyć go w połączeniu z
logging
modułem z Pythona, jeśli chcesz.źródło
(Ach, po prostu przeczytaj ponownie swoje pytanie i zobacz, że to nie do końca dotyczy.)
Oto przykładowy program, który wykorzystuje moduł logowania w języku Python . Ten moduł logowania był we wszystkich wersjach od 2.3. W tym przykładzie rejestrowanie można konfigurować za pomocą opcji wiersza poleceń.
W trybie cichym będzie logował się tylko do pliku, w trybie normalnym będzie logował się zarówno do pliku, jak i do konsoli.
źródło
Aby uzupełnić odpowiedź Johna T: https://stackoverflow.com/a/616686/395687
Dodałem
__enter__
i__exit__
metody, aby użyć go jako menedżera kontekstu zewith
słowem kluczowym, które daje ten kodMoże być następnie używany jako plik
źródło
__del__
funkcjonalność do__exit__
__del__
to zły pomysł. Powinien zostać przeniesiony do wywoływanej funkcji „zamknij”__exit__
.Wiem, że ta kwestia była wielokrotnie odpowiedział, ale za to wziąłem główną odpowiedź od Johna T za odpowiedź i modyfikować je tak, że zawiera sugerowany kolor i następnie jego połączoną wersję poprawioną. Dodałem również wejście i wyjście, jak wspomniano w odpowiedzi cladmi do użycia z instrukcją with. Ponadto dokumentacja wspomina o opróżnianiu plików za pomocą,
os.fsync()
więc również to dodałem. Nie wiem, czy naprawdę tego potrzebujesz, ale jest tam.Możesz go następnie użyć
lub
źródło
mode="ab"
iwwrite
funkcjiself.file.write(message.encode("utf-8"))
inne rozwiązanie wykorzystujące moduł logowania:
źródło
Żadna z powyższych odpowiedzi nie wydaje się naprawdę odpowiadać na postawiony problem. Wiem, że to stary wątek, ale myślę, że ten problem jest o wiele prostszy niż wszyscy:
Teraz to powtórzy wszystko do normalnego programu obsługi sys.stderr i twojego pliku. Utwórz kolejną klasę
tee_out
dlasys.stdout
.źródło
tee=tee_err();tee.write('');tee.write('');...
opens + zamyka plik dla każdegowrite
. Zobacz stackoverflow.com/q/4867468 i stackoverflow.com/q/164053, aby zapoznać się z argumentami przeciwko tej praktyce.Jak na żądanie przez @ user5359531 w komentarzach pod @John T za odpowiedź , oto kopia odwołania pocztą na zmienionej wersji połączonej dyskusji w tej odpowiedzi:
źródło
Piszę skrypt do uruchamiania skryptów linii cmd. (Ponieważ w niektórych przypadkach po prostu nie ma realnego substytutu dla polecenia Linuksa - na przykład w przypadku rsync).
To, czego naprawdę chciałem, to użyć domyślnego mechanizmu logowania w Pythonie w każdym przypadku, gdy było to możliwe, ale nadal wychwytywać każdy błąd, gdy coś poszło nie tak, co było nieoczekiwane.
Ten kod wydaje się działać. Może nie być szczególnie eleganckie lub wydajne (chociaż nie używa string + = string, więc przynajmniej nie ma tego konkretnego potencjalnego wąskiego gardła). Publikuję to na wypadek, gdyby ktoś inny podał jakieś przydatne pomysły.
Oczywiście, jeśli nie jesteś tak podatny na kaprysy jak ja, zamień LOG_IDENTIFIER na inny ciąg, którego nie lubisz widzieć, jak ktoś pisze do dziennika.
źródło
Jeśli chcesz zarejestrować wszystkie dane wyjściowe do pliku ORAZ zapisać je do pliku tekstowego, możesz wykonać następujące czynności. To trochę hacky, ale działa:
EDYCJA: Zauważ, że nie rejestruje to błędów, chyba że przekierujesz sys.stderr do sys.stdout
EDIT2: Drugą kwestią jest to, że musisz przekazać 1 argument w przeciwieństwie do funkcji wbudowanej.
EDIT3: Zobacz kod przed zapisaniem stdin i stdout w konsoli i pliku z stderr tylko przechodzącym do pliku
źródło
Napisałem pełny zamiennik dla
sys.stderr
i po prostu zduplikowałem zmianę nazwy kodustderr
na,stdout
aby udostępnić go również do zastąpieniasys.stdout
.W tym celu należy utworzyć ten sam typ obiektu jako obecny
stderr
istdout
, i przekazuje wszystkie metody do pierwotnego systemustderr
istdout
:Aby z tego skorzystać, możesz po prostu zadzwonić
StdErrReplament::lock(logger)
iStdOutReplament::lock(logger)
przekazać rejestrator, którego chcesz użyć do wysłania tekstu wyjściowego. Na przykład:Uruchamiając ten kod, zobaczysz na ekranie:
A na zawartości pliku:
Jeśli chcesz zobaczyć również zawartość
log.debug
wywołań na ekranie, musisz dodać obsługę strumienia do swojego rejestratora. W tym przypadku wyglądałoby to tak:Który wyświetli się w ten sposób podczas uruchamiania:
Chociaż nadal zapisywałoby to do pliku
my_log_file.txt
:Wyłączenie tego za pomocą
StdErrReplament:unlock()
przywróci tylko standardowe zachowaniestderr
strumienia, ponieważ dołączonego programu rejestrującego nie można nigdy odłączyć, ponieważ ktoś inny może mieć odniesienie do jego starszej wersji. Dlatego jest to globalny singleton, który nigdy nie umiera. Dlatego w przypadku ponownego załadowania tego modułu za pomocąimp
lub czegoś innego, nigdy nie odzyska on prądu,sys.stderr
ponieważ został już do niego wstrzyknięty i zostanie zapisany wewnętrznie.źródło