diff --git a/pullnrun/_log.py b/pullnrun/_log.py
index 10ce05b..dd06f9a 100644
--- a/pullnrun/_log.py
+++ b/pullnrun/_log.py
@@ -1,7 +1,9 @@
 from os import getenv
 from uuid import uuid4
 
-from pullnrun._utils import get_log_entry, timestamp
+from requests import request
+
+from pullnrun._utils import as_list, get_log_entry, timestamp
 
 def _status(output_dict):
     status = output_dict.get('status')
@@ -100,16 +102,31 @@ def log_to_console(output_dict):
         end = '\n' if output[-1] != '\n' else ''
         print(f'\n{output}{end}')
 
+def log_to_url(url, id_, log_entry, method=None):
+    data = {
+        'id': id_,
+        'entry': log_entry,
+    }
+    request(method or 'POST', url, json=data)
+    # TODO: communicate errors
+
 class Log:
-    def __init__(self, quiet=False):
+    def __init__(self, quiet=False, targets=None):
         self._start = None
         self._end = None
         self._id = getenv('PULLNRUN_ID', str(uuid4()))
         self._to_console = not quiet
+        self._targets = as_list(targets)
 
-    def __call__(self, log_entry):
+    def _log(self, log_entry):
         if self._to_console:
             log_to_console(log_entry)
+        for target in self._targets:
+            if target.get('to') == 'url':
+                log_to_url(target.get('url'), self._id, log_entry, target.get('method'))
+
+    def __call__(self, log_entry):
+        self._log(log_entry)
 
     @property
     def id(self):
@@ -117,11 +134,13 @@ class Log:
 
     def start(self):
         self._start = timestamp()
-        if self._to_console:
-            log_to_console(get_log_entry('main', 'STARTED', start=self._start, id=self._id))
+        self._log(
+            get_log_entry('main', 'STARTED', start=self._start, id=self._id)
+        )
 
     def end(self, success, fail):
         self._end = timestamp()
         status = 'SUCCESS' if success > 0 and fail == 0 else 'ERROR'
-        if self._to_console:
-            log_to_console(get_log_entry('main', status, start=self._start, end=self._end, success=success, fail=fail))
+        self._log(
+            get_log_entry('main', status, start=self._start, end=self._end, success=success, fail=fail)
+        )
diff --git a/pullnrun/_main.py b/pullnrun/_main.py
index d7cea31..da00464 100644
--- a/pullnrun/_main.py
+++ b/pullnrun/_main.py
@@ -27,7 +27,8 @@ def _validate(input_dict):
 def main(input_dict, quiet=False):
     _validate(input_dict)
 
-    log = Log(quiet)
+    log_targets = input_dict.get('log')
+    log = Log(quiet, log_targets)
     log.start()
 
     success, error = (0, 0, )
diff --git a/pullnrun/schema.json b/pullnrun/schema.json
index 6b88b71..17961af 100644
--- a/pullnrun/schema.json
+++ b/pullnrun/schema.json
@@ -84,6 +84,20 @@
 				"directory": { "type": "string" }
 			},
 			"required": [ "command" ]
+		},
+		"log_http": {
+			"type": "object",
+			"properties": {
+				"to": { "const": "url" },
+				"url": { "type": "string" },
+				"method": {"type": "string" }
+			},
+			"required": [ "to", "url" ]
+		},
+		"log": {
+			"oneOf": [
+				{ "$ref": "#/definitions/log_http" }
+			]
 		}
 	},
 	"type": "object",
@@ -114,6 +128,15 @@
 				},
 				{ "$ref" :"#/definitions/run" }
 			]
+		},
+		"log": {
+			"oneOf": [
+				{
+					"type": "array",
+					"items": { "$ref" :"#/definitions/log" }
+				},
+				{ "$ref" :"#/definitions/log" }
+			]
 		}
 	}
 }
\ No newline at end of file

Add logging to remote target
diff --git a/tst/test_log.py b/tst/test_log.py
index 5db69da..95025cf 100644
--- a/tst/test_log.py
+++ b/tst/test_log.py
@@ -90,3 +90,25 @@ class LogTest(TestCase):
             output = print_mock.call_args[-2][0]
 
             self.assertIn(result, output)
+
+    @patch('builtins.print')
+    @patch('pullnrun._log.request')
+    def test_log_sends_entries_via_http(self, request_mock, print_mock):
+        log = Log(quiet=True, targets={'to': 'url', 'url': 'test_url', 'method': 'PUT'})
+
+        entry = {'type': 'run', 'status': 'STARTED'}
+        log(entry)
+
+        request_mock.assert_called_with('PUT', 'test_url', json={'id': log.id, 'entry': entry})
+        print_mock.assert_not_called()
+
+    @patch('builtins.print')
+    @patch('pullnrun._log.request')
+    def test_log_sends_entries_as_post_by_default(self, request_mock, print_mock):
+        log = Log(quiet=True, targets={'to': 'url', 'url': 'test_url'})
+
+        entry = {'type': 'run', 'status': 'STARTED'}
+        log(entry)
+
+        request_mock.assert_called_with('POST', 'test_url', json={'id': log.id, 'entry': entry})
+        print_mock.assert_not_called()

pip install -e .
parse_log_pytest
requirements.txt
coverage pylint pytest
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
astroid==2.15.8
attrs==24.2.0
boto3==1.33.13
botocore==1.33.13
certifi @ file:///croot/certifi_1671487769961/work/certifi
charset-normalizer==3.4.1
coverage==7.2.7
dill==0.3.7
exceptiongroup==1.2.2
idna==3.10
importlib-metadata==6.7.0
importlib-resources==5.12.0
iniconfig==2.0.0
isort==5.11.5
jmespath==1.0.1
jsonschema==4.17.3
lazy-object-proxy==1.9.0
mccabe==0.7.0
packaging==24.0
pkgutil_resolve_name==1.3.10
platformdirs==4.0.0
pluggy==1.2.0
-e git+https://github.com/kangasta/pullnrun.git@10267083359522e5cac8db10ff7b95738aa7607d#egg=pullnrun
pylint==2.17.7
pyrsistent==0.19.3
pytest==7.4.4
python-dateutil==2.9.0.post0
requests==2.31.0
s3transfer==0.8.2
six==1.17.0
swebench-matterhorn @ file:///swebench_matterhorn
tomli==2.0.1
tomlkit==0.12.5
typed-ast==1.5.5
typing_extensions==4.7.1
urllib3==1.26.20
wrapt==1.16.0
zipp==3.15.0

name: pullnrun
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - astroid==2.15.8
      - attrs==24.2.0
      - boto3==1.33.13
      - botocore==1.33.13
      - charset-normalizer==3.4.1
      - coverage==7.2.7
      - dill==0.3.7
      - exceptiongroup==1.2.2
      - idna==3.10
      - importlib-metadata==6.7.0
      - importlib-resources==5.12.0
      - iniconfig==2.0.0
      - isort==5.11.5
      - jmespath==1.0.1
      - jsonschema==4.17.3
      - lazy-object-proxy==1.9.0
      - mccabe==0.7.0
      - packaging==24.0
      - pkgutil-resolve-name==1.3.10
      - platformdirs==4.0.0
      - pluggy==1.2.0
      - pylint==2.17.7
      - pyrsistent==0.19.3
      - pytest==7.4.4
      - python-dateutil==2.9.0.post0
      - requests==2.31.0
      - s3transfer==0.8.2
      - six==1.17.0
      - swebench-matterhorn==0.0.0
      - tomli==2.0.1
      - tomlkit==0.12.5
      - typed-ast==1.5.5
      - typing-extensions==4.7.1
      - urllib3==1.26.20
      - wrapt==1.16.0
      - zipp==3.15.0
prefix: /opt/conda/envs/pullnrun

tst/test_log.py::LogTest::test_log_sends_entries_as_post_by_default tst/test_log.py::LogTest::test_log_sends_entries_via_http
tst/test_log.py::LogTest::test_duration_is_logged_correctly tst/test_log.py::LogTest::test_log_class_prints_actions tst/test_log.py::LogTest::test_log_to_console_does_not_crash_on_empty_input tst/test_log.py::LogTest::test_log_to_console_prints_logs_as_specified tst/test_log.py::LogTest::test_log_to_console_prints_run_output
pullnrun/_log.py pullnrun/_main.py pullnrun/schema.json
pullnrun/_log.py pullnrun/_main.py pullnrun/schema.json