wyjście na żywo z polecenia podprocess
Używam skryptu Pythona jako Sterownika kodu hydrodynamicznego. Kiedy przychodzi czas na uruchomienie symulacji, używam subprocess.Popen
do uruchomienia kodu, zbieram dane wyjściowe ze stdout i stderr do subprocess.PIPE
- - - Następnie mogę wydrukować (i zapisać do pliku dziennika) informacje wyjściowe i sprawdzić, czy nie ma błędów. Problem w tym, że nie mam pojęcia, jak postępuje kod. Jeśli uruchamiam go bezpośrednio z linii poleceń, to daje mi wyjście o tym, co iteracja jego w, o której godzinie, co następnym krokiem jest, itd.
Czy istnieje sposób, aby zarówno przechowywać dane wyjściowe (do rejestrowania i sprawdzania błędów), jak i produkować wyjście strumieniowe na żywo?
Odpowiedni fragment mojego kodu:
ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
print "RUN failed\n\n%s\n\n" % (errors)
success = False
if( errors ): log_file.write("\n\n%s\n\n" % errors)
Początkowo przepuszczałem run_command
przez tee
tak, że kopia poszła bezpośrednio do pliku dziennika, a strumień nadal wypływa bezpośrednio do terminala -- ale w ten sposób nie mogę przechowywać żadnych błędów (według mojej wiedzy).
Edit:
Rozwiązanie tymczasowe:
ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
log_file.flush()
Wtedy, w kolejny terminal, Uruchom tail -f log.txt
(s. t. log_file = 'log.txt'
).
12 answers
Można to zrobić na dwa sposoby, tworząc iterator z funkcji read
lub readline
i wykonując:
import subprocess
import sys
with open('test.log', 'w') as f:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for c in iter(lambda: process.stdout.read(1), ''): # replace '' with b'' for Python 3
sys.stdout.write(c)
f.write(c)
Lub
import subprocess
import sys
with open('test.log', 'w') as f:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in iter(process.stdout.readline, ''): # replace '' with b'' for Python 3
sys.stdout.write(line)
f.write(line)
Lub możesz utworzyć plik reader
i writer
. Przekaż writer
do Popen
i odczytaj z reader
import io
import time
import subprocess
import sys
filename = 'test.log'
with io.open(filename, 'wb') as writer, io.open(filename, 'rb', 1) as reader:
process = subprocess.Popen(command, stdout=writer)
while process.poll() is None:
sys.stdout.write(reader.read())
time.sleep(0.5)
# Read the remaining
sys.stdout.write(reader.read())
W ten sposób dane będą zapisywane zarówno w test.log
, jak i na standardowym wyjściu.
Jedyną zaletą podejścia do plików jest to, że Twój kod nie blokuje się. Więc w międzyczasie możesz robić, co chcesz. i czytać kiedy chcesz z reader
w sposób nieblokujący. Kiedy używasz PIPE
, read
i readline
funkcje będą blokować, dopóki jeden znak nie zostanie zapisany do rury lub linia zostanie zapisana odpowiednio do rury.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-18 04:55:33
Streszczenie (lub wersja" TL;dr"): jest łatwo, gdy jest co najwyżej jedna subprocess.PIPE
, w przeciwnym razie jest ciężko.
To może być czas, aby wyjaśnić trochę o tym, jak subprocess.Popen
robi swoje rzeczy.
(Zastrzeżenie: to jest dla Pythona 2.x, chociaż 3.x jest podobny; i jestem dość niewyraźny w wariancie Windows. Rozumiem rzeczy POSIX znacznie lepiej.)
Funkcja Popen
musi obsługiwać od zera do trzech strumieni We/Wy, nieco jednocześnie. Są one oznaczone stdin
, stdout
, i stderr
jako to co zwykle.
Możesz podać:
-
None
, wskazując, że nie chcesz przekierowywać strumienia. Odziedziczy je jak zwykle. Zauważ, że przynajmniej w systemach POSIX, nie oznacza to, że będzie używać Pythonasys.stdout
, tylko Pythona actual stdout; patrz demo na końcu. - An
int
wartość. Jest to "surowy" deskryptor pliku (przynajmniej w POSIX). (Uwaga:PIPE
iSTDOUT
są wewnętrznieint
, ale są" niemożliwymi " deskryptorami, -1 i -2.) - strumień-właściwie każdy obiekt z metodą
fileno
.Popen
znajdzie deskryptor dla tego strumienia, używającstream.fileno()
, a następnie postępować tak, jak dla wartościint
. -
subprocess.PIPE
, wskazując, że Python powinien utworzyć rurę. -
subprocess.STDOUT
(tylko dlastderr
): powiedz Pythonowi, aby używał tego samego deskryptora, co dlastdout
. Ma to sens tylko wtedy, gdy podałeś (nie-None
) wartość dlastdout
, a nawet wtedy, jest ona potrzebna tylko , jeśli Ustawiłeśstdout=subprocess.PIPE
. (W przeciwnym razie można po prostu podaj ten sam argument, który podałeś dlastdout
, np.Popen(..., stdout=stream, stderr=stream)
.)
Najprostsze przypadki (bez rur)
Jeśli nic nie przekierujesz (pozostaw wszystkie trzy jako domyślną wartość None
lub podaj jawną None
), Pipe
ma to dość proste. Musi tylko wyłączyć podproces i pozwolić mu działać. Albo, jeśli przekierujesz do Nie - PIPE
- an int
lub strumienia fileno()
- nadal jest to łatwe, ponieważ system operacyjny wykonuje całą pracę. Pythona wystarczy wyłączyć podproces, łącząc jego stdin, stdout i / lub stderr do dostarczonych deskryptorów plików.
Sprawa still-easy: jedna rura
Jeśli przekierujesz tylko jeden strumień, Pipe
nadal ma rzeczy dość łatwe. Wybierzmy jeden strumień na raz i oglądajmy.
Załóżmy, że chcesz podać jakieś stdin
, ale pozwól stdout
i {[11] } przejść bez przekierowania lub przejść do deskryptora pliku. Jako proces macierzysty, Twój program w Pythonie musi po prostu użyć write()
, aby wysłać dane w dół potoku. Możesz to zrobić sam, np.:
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc
Lub możesz przekazać dane stdin do proc.communicate()
, które następnie wykonuje stdin.write
pokazane powyżej. Nie ma wyjścia, więc communicate()
ma tylko jedno prawdziwe zadanie: zamyka rurę za Ciebie. (Jeśli nie zadzwonisz proc.communicate()
, musisz zadzwonić proc.stdin.close()
, aby zamknąć rurę, aby podproces wiedział, że nie ma więcej danych.)
Załóżmy, że chcesz schwytać stdout
ale zostaw stdin
i stderr
w spokoju. Ponownie, to proste: wystarczy zadzwonić proc.stdout.read()
(lub równoważne), aż nie ma większa wydajność. Ponieważ proc.stdout()
jest normalnym strumieniem We/Wy Pythona, możesz użyć na nim wszystkich normalnych konstrukcji, takich jak:
for line in proc.stdout:
Lub, ponownie, możesz użyć proc.communicate()
, który po prostu robi read()
za Ciebie.
Jeśli chcesz przechwytywać tylko stderr
, działa to tak samo jak w przypadku stdout
.
stdout
, a także przechwycić stderr
, ale na tej samej rurze co stdout:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
W tym przypadku, subprocess
"oszukuje"! Cóż, musi to zrobić, więc nie jest to oszustwo: uruchamia podproces zarówno ze swoim stdout, jak i ze stderr skierowanym do (pojedynczego) deskryptora potoku, który przekierowuje z powrotem do jego macierzystego (Pythona) procesu. Po stronie nadrzędnej znajduje się ponownie tylko jeden deskryptor rury do odczytu wyjścia. Wszystkie wyjścia "stderr" pojawiają się w proc.stdout
, a jeśli wywołasz proc.communicate()
, wynikiem stderr (druga wartość krotki) będzie None
, a nie Łańcuch znaków.
Twarde przypadki: dwa lub więcej rury
Problemy pojawiają się, gdy chcesz użyć przynajmniej dwóch rur. W rzeczywistości kod subprocess
ma ten bit:
def communicate(self, input=None):
...
# Optimization: If we are only using one pipe, or no pipe at
# all, using select() or threads is unnecessary.
if [self.stdin, self.stdout, self.stderr].count(None) >= 2:
Ale, niestety, tutaj zrobiliśmy co najmniej dwie, a może trzy, różne rury, więc count(None)
zwraca albo 1, albo 0. Musimy robić rzeczy w trudny sposób.
W systemie Windows, To używa {[64] } do gromadzenia wyników dla self.stdout
i self.stderr
, i sprawia, że wątek nadrzędny dostarcza self.stdin
dane wejściowe (a następnie zamyka przewód).
Na POSIX, to używa poll
, jeśli jest dostępny, w przeciwnym razie select
, do gromadzenia danych wyjściowych i dostarczania wejścia stdin. Wszystko to działa w (pojedynczym) procesie rodzica/wątku.
Threads lub poll / select są potrzebne tutaj, aby uniknąć impasu. Załóżmy na przykład, że przekierowaliśmy wszystkie trzy strumienie do trzech oddzielnych rur. Załóżmy ponadto, że istnieje mały limit ilości danych, które mogą być włożone do rury, zanim proces zapisu zostanie zawieszony, czekając na proces odczytu, aby" wyczyścić " rurę z drugi koniec. Ustawmy ten mały limit na jeden bajt, tylko dla ilustracji. (W rzeczywistości tak to działa, z tym wyjątkiem, że limit jest znacznie większy niż jeden bajt.)
Jeśli proces macierzysty (Python) próbuje zapisać kilka bajtów-powiedzmy, 'go\n'
do proc.stdin
, pierwszy bajt wchodzi do środka, a drugi powoduje zawieszenie procesu Pythona, czekając na podproces odczytujący pierwszy bajt, opróżniając rurę.
Tymczasem Załóżmy, że podproces decyduje się wydrukować przyjazną "Halo! Nie panikuj!"pozdrawiam. W przeciwieństwie do tego, że nie jest on w stanie odczytać kodu, nie jest on w stanie odczytać kodu źródłowego.
Teraz utknęliśmy: proces Pythona śpi, czeka na zakończenie mówiąc "go", a podproces również śpi, czeka na zakończenie mówiąc " Hello! Nie panikuj!".
Kod subprocess.Popen
unika tego problemu z threading-or-select / poll. Gdy bajty mogą przejść przez rury, idą. Kiedy nie mogą, tylko wątek (nie cały proces) musi być uśpiony-lub, w przypadku select / poll, proces Pythona czeka jednocześnie na "can write" lub "data available", zapisuje na stdin procesu tylko wtedy, gdy jest miejsce i odczytuje jego stdout i/lub stderr tylko wtedy, gdy dane są gotowe. Kod proc.communicate()
(w rzeczywistości _communicate
, w którym obsługiwane są przypadki hairy) zwraca po wysłaniu wszystkich danych stdin (o ile istnieją) i zgromadzeniu wszystkich danych stdout i / lub stderr.
Jeśli chcesz przeczytać zarówno stdout
i stderr
na dwóch różnych rurach (niezależnie od jakiegokolwiek przekierowania stdin
), musisz również uniknąć impasu. Scenariusz impasu jest tutaj inny-występuje, gdy podproces pisze coś długiego do stderr
podczas pobierania danych z stdout
, lub odwrotnie-ale nadal tam jest.
Demo
Obiecałem, że zademonstruję, że, bez przekierowania, Python subprocess
es zapisuje do bazowego stdout, a nie sys.stdout
. Oto kod:
from cStringIO import StringIO
import os
import subprocess
import sys
def show1():
print 'start show1'
save = sys.stdout
sys.stdout = StringIO()
print 'sys.stdout being buffered'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
in_stdout = sys.stdout.getvalue()
sys.stdout = save
print 'in buffer:', in_stdout
def show2():
print 'start show2'
save = sys.stdout
sys.stdout = open(os.devnull, 'w')
print 'after redirect sys.stdout'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
sys.stdout = save
show1()
show2()
Kiedy przebieg:
$ python out.py
start show1
hello
in buffer: sys.stdout being buffered
start show2
hello
Zauważ, że pierwsza procedura zakończy się niepowodzeniem, jeśli dodasz stdout=sys.stdout
, ponieważ StringIO
obiekt nie ma fileno
. Drugi pomija hello
, jeśli dodasz stdout=sys.stdout
, ponieważ sys.stdout
zostało przekierowane na os.devnull
.
(jeśli przekierujesz plik Pythona-deskryptor-1, podproces podąży za tym przekierowaniem. Wywołanie open(os.devnull, 'w')
wytwarza strumień, którego {[36] } jest większy niż 2.)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-24 20:51:33
Jeśli możesz korzystać z bibliotek innych firm, możesz użyć czegoś takiego jak sarge
(ujawnienie: jestem jego opiekunem). Biblioteka ta umożliwia nieblokujący dostęp do strumieni wyjściowych z podprocesów-jest warstwowa nad modułem subprocess
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-24 16:00:14
Możemy również użyć domyślnego iteratora pliku do odczytu stdout zamiast używać ITER construct z readline ().
import subprocess
import sys
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
for line in process.stdout:
sys.stdout.write(line)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-13 01:37:08
Dobrym, ale" ciężkim " rozwiązaniem jest użycie Twisted-patrz dno.
Jeśli chcesz żyć tylko ze stdout coś w tym stylu powinno działać:
import subprocess
import sys
popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE)
while not popenobj.poll():
stdoutdata = popenobj.stdout.readline()
if stdoutdata:
sys.stdout.write(stdoutdata)
else:
break
print "Return code", popenobj.returncode
(Jeśli używasz read (), to próbuje odczytać cały "plik", który nie jest użyteczny, to co naprawdę możemy tu wykorzystać to coś, co odczytuje wszystkie dane znajdujące się w potoku)
Można też próbować podejść do tego z threadingiem, np.:
import subprocess
import sys
import threading
popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, shell=True)
def stdoutprocess(o):
while True:
stdoutdata = o.stdout.readline()
if stdoutdata:
sys.stdout.write(stdoutdata)
else:
break
t = threading.Thread(target=stdoutprocess, args=(popenobj,))
t.start()
popenobj.wait()
t.join()
print "Return code", popenobj.returncode
Teraz możemy potencjalnie dodać stderr również przez posiadające dwie nitki.
Zauważ jednak, że podprocesowe dokumenty zniechęcają do bezpośredniego korzystania z tych plików i zalecają użycie communicate()
(głównie dotyczy to blokad, które moim zdaniem nie są problemem powyżej), a rozwiązania są trochę klunky, więc naprawdę wydaje się, że Moduł podprocesowy nie jest do końca odpowiedni do zadania (Zobacz także: http://www.python.org/dev/peps/pep-3145 / ) i musimy spojrzeć na coś innego.
Bardziej zaangażowanym rozwiązaniem jest użycie Twisted jako pokazane tutaj: https://twistedmatrix.com/documents/11.1.0/core/howto/process.html
Sposób, w jaki robisz to za pomocą Twisted, polega na utworzeniu procesu za pomocą reactor.spawnprocess()
i dostarczeniu ProcessProtocol
, który następnie przetwarza wyjście asynchronicznie. Pokręcony przykładowy kod Pythona jest tutaj: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-24 21:35:15
Wygląda na to, że wyjście buforowane liniowo będzie działać dla Ciebie, w takim przypadku może pasować coś podobnego do następującego. (Zastrzeżenie: to nieprzetestowane.) To da tylko stdout podprocesu w czasie rzeczywistym. Jeśli chcesz mieć zarówno stderr jak i stdout w czasie rzeczywistym, musisz zrobić coś bardziej skomplikowanego za pomocą select
.
proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
while proc.poll() is None:
line = proc.stdout.readline()
print line
log_file.write(line + '\n')
# Might still be data on stdout at this point. Grab any
# remainder.
for line in proc.stdout.read().split('\n'):
print line
log_file.write(line + '\n')
# Do whatever you want with proc.stderr here...
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-24 19:21:35
Dlaczego nie ustawić stdout
bezpośrednio na sys.stdout
? I jeśli chcesz również wypisać do dziennika, możesz po prostu nadpisać metodę zapisu f.
import sys
import subprocess
class SuperFile(open.__class__):
def write(self, data):
sys.stdout.write(data)
super(SuperFile, self).write(data)
f = SuperFile("log.txt","w+")
process = subprocess.Popen(command, stdout=f, stderr=f)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-04 19:54:16
Oto Klasa, której używam w jednym z moich projektów. Przekierowuje wyjście podprocesu do dziennika. Na początku próbowałem po prostu nadpisać metodę write, ale to nie działa, ponieważ podproces nigdy jej nie wywoła (przekierowanie odbywa się na poziomie filedescriptor). Więc używam własnej rury, podobnie jak to się robi w podproces-module. Zaletą tego rozwiązania jest hermetyzacja całej logiki logowania/drukowania w adapterze i można po prostu przekazać instancje rejestratora do Popen
: subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))
class LogAdapter(threading.Thread):
def __init__(self, logname, level = logging.INFO):
super().__init__()
self.log = logging.getLogger(logname)
self.readpipe, self.writepipe = os.pipe()
logFunctions = {
logging.DEBUG: self.log.debug,
logging.INFO: self.log.info,
logging.WARN: self.log.warn,
logging.ERROR: self.log.warn,
}
try:
self.logFunction = logFunctions[level]
except KeyError:
self.logFunction = self.log.info
def fileno(self):
#when fileno is called this indicates the subprocess is about to fork => start thread
self.start()
return self.writepipe
def finished(self):
"""If the write-filedescriptor is not closed this thread will
prevent the whole program from exiting. You can use this method
to clean up after the subprocess has terminated."""
os.close(self.writepipe)
def run(self):
inputFile = os.fdopen(self.readpipe)
while True:
line = inputFile.readline()
if len(line) == 0:
#no new data was added
break
self.logFunction(line.strip())
Jeśli nie potrzebujesz logowania, ale po prostu chcesz użyć print()
, możesz oczywiście usunąć duże części kodu i zachować klasę krótszą. Możesz również rozwinąć go za pomocą metody __enter__
i __exit__
i wywołać finished
w __exit__
, aby łatwo użyć go jako kontekstu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-21 14:31:26
Wszystkie powyższe rozwiązania nie powiodły się albo do oddzielenia wyjścia stderr i stdout, (wiele rur) lub zablokowane na zawsze, gdy bufor rur OS był pełny, co dzieje się, gdy polecenie uruchamiasz wyjścia zbyt szybko (jest ostrzeżenie dla tego w Python poll() podręcznik podprocesu). Jedynym niezawodnym sposobem, jaki znalazłem, było select, ale jest to rozwiązanie wyłącznie posix:
import subprocess
import sys
import os
import select
# returns command exit status, stdout text, stderr text
# rtoutput: show realtime output while running
def run_script(cmd,rtoutput=0):
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
poller = select.poll()
poller.register(p.stdout, select.POLLIN)
poller.register(p.stderr, select.POLLIN)
coutput=''
cerror=''
fdhup={}
fdhup[p.stdout.fileno()]=0
fdhup[p.stderr.fileno()]=0
while sum(fdhup.values()) < len(fdhup):
try:
r = poller.poll(1)
except select.error, err:
if err.args[0] != EINTR:
raise
r=[]
for fd, flags in r:
if flags & (select.POLLIN | select.POLLPRI):
c = os.read(fd, 1024)
if rtoutput:
sys.stdout.write(c)
sys.stdout.flush()
if fd == p.stderr.fileno():
cerror+=c
else:
coutput+=c
else:
fdhup[fd]=1
return p.poll(), coutput.strip(), cerror.strip()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-25 14:25:16
Oprócz wszystkich tych odpowiedzi, jedno proste podejście może być również następujące:
process = subprocess.Popen(your_command, stdout=subprocess.PIPE)
while process.stdout.readable():
line = rstrm.readline()
if not line:
break
print(line.strip())
Pętla przez czytelny strumień tak długo, jak jest czytelny i jeśli otrzyma pusty wynik, zatrzymaj go.
Kluczem jest to, że readline()
zwraca linię (z {[2] } na końcu) tak długo, jak jest wyjście i jest puste, jeśli naprawdę jest na końcu.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-30 07:32:01
Zaryzykuję tutaj i zaproponuję użycie check_call
. Zgodnie z dokumentacją, uruchamia operację blokowania i pasuje do tego celu całkiem dobrze.
Uruchom polecenie z argumentami. Poczekaj na zakończenie. Jeśli zwracany kod to zero, a następnie return, w przeciwnym razie raise CalledProcessError. Obiekt CalledProcessError będzie miał kod powrotu w atrybut returncode.
cmd = "some_crazy_long_script.sh"
args = {
'shell': True,
'cwd': if_you_need_it
}
subprocess.check_call(cmd, **args)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-24 16:49:12
import sys
import subprocess
f = open("log.txt","w+")
process = subprocess.Popen(command, stdout=subprocess.PIPE)
for line in iter(process.stdout.readline, ''):
sys.stdout.write(line)
f.write(line.replace("\n",""))
f.close()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-29 11:54:28