diff --git a/azurelinuxagent/common/event.py b/azurelinuxagent/common/event.py
index 9265820a..fb2c97c7 100644
--- a/azurelinuxagent/common/event.py
+++ b/azurelinuxagent/common/event.py
@@ -65,8 +65,17 @@ class EventLogger(object):
         if not os.path.exists(self.event_dir):
             os.mkdir(self.event_dir)
             os.chmod(self.event_dir, 0o700)
-        if len(os.listdir(self.event_dir)) > 1000:
-            raise EventError("Too many files under: {0}".format(self.event_dir))
+
+        existing_events = os.listdir(self.event_dir)
+        if len(existing_events) >= 1000:
+            existing_events.sort()
+            oldest_files = existing_events[:-999]
+            logger.warn("Too many files under: {0}, removing oldest".format(self.event_dir))
+            try:
+                for f in oldest_files:
+                    os.remove(os.path.join(self.event_dir, f))
+            except IOError as e:
+                raise EventError(e)
 
         filename = os.path.join(self.event_dir,
                                 ustr(int(time.time() * 1000000)))
diff --git a/azurelinuxagent/ga/exthandlers.py b/azurelinuxagent/ga/exthandlers.py
index 70bb246e..9d3cec3f 100644
--- a/azurelinuxagent/ga/exthandlers.py
+++ b/azurelinuxagent/ga/exthandlers.py
@@ -221,9 +221,9 @@ class ExtHandlersHandler(object):
 
             self.log_etag = True
 
-            state = ext_handler.properties.state
+            state = ext_handler.properties.state.lower()
             ext_handler_i.logger.info("Expected handler state: {0}", state)
-            if state == "enabled":
+            if state == u"enabled":
                 self.handle_enable(ext_handler_i)
             elif state == u"disabled":
                 self.handle_disable(ext_handler_i)
@@ -709,7 +709,7 @@ class ExtHandlerInstance(object):
             heartbeat = json.loads(heartbeat_json)[0]['heartbeat']
         except IOError as e:
             raise ExtensionError("Failed to get heartbeat file:{0}".format(e))
-        except ValueError as e:
+        except (ValueError, KeyError) as e:
             raise ExtensionError("Malformed heartbeat file: {0}".format(e))
         return heartbeat
  
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index 53f01736..61d9f470 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -27,6 +27,7 @@ import signal
 import subprocess
 import sys
 import time
+import traceback
 import zipfile
 
 import azurelinuxagent.common.conf as conf
@@ -250,6 +251,7 @@ class UpdateHandler(object):
 
         except Exception as e:
             logger.warn(u"Agent {0} failed with exception: {1}", CURRENT_AGENT, ustr(e))
+            logger.warn(traceback.format_exc())
             sys.exit(1)
             return
 

Agent crash should log traceback
When the extension handler crashes we get a generic 'agent crashed', we should log a traceback here.
diff --git a/tests/common/test_event.py b/tests/common/test_event.py
new file mode 100644
index 00000000..726474cd
--- /dev/null
+++ b/tests/common/test_event.py
@@ -0,0 +1,90 @@
+# Copyright 2017 Microsoft Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Requires Python 2.4+ and Openssl 1.0+
+#
+
+from __future__ import print_function
+
+from tests.tools import *
+from azurelinuxagent.common.event import *
+
+
+class TestEvent(AgentTestCase):
+    def test_save_event(self):
+        tmp_evt = tempfile.mkdtemp()
+        init_event_logger(tmp_evt)
+        add_event('test', message='test event')
+        self.assertTrue(len(os.listdir(tmp_evt)) == 1)
+        shutil.rmtree(tmp_evt)
+
+    def test_save_event_rollover(self):
+        tmp_evt = tempfile.mkdtemp()
+        init_event_logger(tmp_evt)
+        add_event('test', message='first event')
+        for i in range(0, 999):
+            add_event('test', message='test event {0}'.format(i))
+
+        events = os.listdir(tmp_evt)
+        events.sort()
+        self.assertTrue(len(events) == 1000)
+
+        first_event = os.path.join(tmp_evt, events[0])
+        with open(first_event) as first_fh:
+            first_event_text = first_fh.read()
+            self.assertTrue('first event' in first_event_text)
+
+        add_event('test', message='last event')
+        events = os.listdir(tmp_evt)
+        events.sort()
+        self.assertTrue(len(events) == 1000, "{0} events found, 1000 expected".format(len(events)))
+
+        first_event = os.path.join(tmp_evt, events[0])
+        with open(first_event) as first_fh:
+            first_event_text = first_fh.read()
+            self.assertFalse('first event' in first_event_text)
+            self.assertTrue('test event 0' in first_event_text)
+
+        last_event = os.path.join(tmp_evt, events[-1])
+        with open(last_event) as last_fh:
+            last_event_text = last_fh.read()
+            self.assertTrue('last event' in last_event_text)
+
+        shutil.rmtree(tmp_evt)
+
+    def test_save_event_cleanup(self):
+        tmp_evt = tempfile.mkdtemp()
+        init_event_logger(tmp_evt)
+
+        for i in range(0, 2000):
+            evt = os.path.join(tmp_evt, '{0}.tld'.format(ustr(1491004920536531 + i)))
+            with open(evt, 'w') as fh:
+                fh.write('test event {0}'.format(i))
+
+        events = os.listdir(tmp_evt)
+        self.assertTrue(len(events) == 2000, "{0} events found, 2000 expected".format(len(events)))
+        add_event('test', message='last event')
+
+        events = os.listdir(tmp_evt)
+        events.sort()
+        self.assertTrue(len(events) == 1000, "{0} events found, 1000 expected".format(len(events)))
+        first_event = os.path.join(tmp_evt, events[0])
+        with open(first_event) as first_fh:
+            first_event_text = first_fh.read()
+            self.assertTrue('test event 1001' in first_event_text)
+
+        last_event = os.path.join(tmp_evt, events[-1])
+        with open(last_event) as last_fh:
+            last_event_text = last_fh.read()
+            self.assertTrue('last event' in last_event_text)
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index 46502cc7..46d06735 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -1083,7 +1083,6 @@ class TestUpdate(UpdateTestCase):
         self._test_run_latest(mock_child=mock_child, mock_time=mock_time)
         self.assertEqual(1, mock_child.poll.call_count)
         self.assertEqual(0, mock_child.wait.call_count)
-        self.assertEqual(2, mock_time.time_call_count)
         return
 
     def test_run_latest_defaults_to_current(self):

pip install -e .
parse_log_pytest
pytest
pyasn1 nose pytest
apt-get update apt-get install -y gcc
3.4
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///opt/conda/conda-bld/attrs_1642510447205/work
certifi==2021.5.30
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1631916693255/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
more-itertools @ file:///tmp/build/80754af9/more-itertools_1637733554872/work
nose==1.3.7
packaging @ file:///tmp/build/80754af9/packaging_1637314298585/work
pluggy @ file:///tmp/build/80754af9/pluggy_1615976315926/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pyasn1==0.5.1
pyparsing @ file:///tmp/build/80754af9/pyparsing_1635766073266/work
pytest==6.2.4
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
typing_extensions @ file:///opt/conda/conda-bld/typing_extensions_1647553014482/work
-e git+https://github.com/Azure/WALinuxAgent.git@c6704cbad83b93763cf2403a77197559a67b9dca#egg=WALinuxAgent
zipp @ file:///tmp/build/80754af9/zipp_1633618647012/work

name: WALinuxAgent
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
  - attrs=21.4.0=pyhd3eb1b0_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - importlib-metadata=4.8.1=py36h06a4308_0
  - importlib_metadata=4.8.1=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - more-itertools=8.12.0=pyhd3eb1b0_0
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=21.3=pyhd3eb1b0_0
  - pip=21.2.2=py36h06a4308_0
  - pluggy=0.13.1=py36h06a4308_0
  - py=1.11.0=pyhd3eb1b0_0
  - pyparsing=3.0.4=pyhd3eb1b0_0
  - pytest=6.2.4=py36h06a4308_2
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - toml=0.10.2=pyhd3eb1b0_0
  - typing_extensions=4.1.1=pyh06a4308_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.6.0=pyhd3eb1b0_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - nose==1.3.7
      - pyasn1==0.5.1
prefix: /opt/conda/envs/WALinuxAgent

tests/common/test_event.py::TestEvent::test_save_event_cleanup tests/common/test_event.py::TestEvent::test_save_event_rollover
tests/common/test_event.py::TestEvent::test_save_event tests/ga/test_update.py::TestGuestAgentError::test_clear tests/ga/test_update.py::TestGuestAgentError::test_creation tests/ga/test_update.py::TestGuestAgentError::test_load_preserves_error_state tests/ga/test_update.py::TestGuestAgentError::test_mark_failure tests/ga/test_update.py::TestGuestAgentError::test_mark_failure_permanent tests/ga/test_update.py::TestGuestAgentError::test_save tests/ga/test_update.py::TestGuestAgentError::test_str tests/ga/test_update.py::TestGuestAgent::test_clear_error tests/ga/test_update.py::TestGuestAgent::test_creation tests/ga/test_update.py::TestGuestAgent::test_download tests/ga/test_update.py::TestGuestAgent::test_download_fail tests/ga/test_update.py::TestGuestAgent::test_download_fallback tests/ga/test_update.py::TestGuestAgent::test_ensure_download_skips_blacklisted tests/ga/test_update.py::TestGuestAgent::test_ensure_downloaded tests/ga/test_update.py::TestGuestAgent::test_ensure_downloaded_download_fails tests/ga/test_update.py::TestGuestAgent::test_ensure_downloaded_load_manifest_fails tests/ga/test_update.py::TestGuestAgent::test_ensure_downloaded_unpack_fails tests/ga/test_update.py::TestGuestAgent::test_is_available tests/ga/test_update.py::TestGuestAgent::test_is_blacklisted tests/ga/test_update.py::TestGuestAgent::test_is_downloaded tests/ga/test_update.py::TestGuestAgent::test_load_error tests/ga/test_update.py::TestGuestAgent::test_load_manifest tests/ga/test_update.py::TestGuestAgent::test_load_manifest_is_empty tests/ga/test_update.py::TestGuestAgent::test_load_manifest_is_malformed tests/ga/test_update.py::TestGuestAgent::test_load_manifest_missing tests/ga/test_update.py::TestGuestAgent::test_mark_failure tests/ga/test_update.py::TestGuestAgent::test_unpack tests/ga/test_update.py::TestGuestAgent::test_unpack_fail tests/ga/test_update.py::TestUpdate::test_creation tests/ga/test_update.py::TestUpdate::test_emit_restart_event_emits_event_if_not_clean_start tests/ga/test_update.py::TestUpdate::test_emit_restart_event_writes_sentinal_file tests/ga/test_update.py::TestUpdate::test_ensure_no_orphans tests/ga/test_update.py::TestUpdate::test_ensure_no_orphans_ignores_exceptions tests/ga/test_update.py::TestUpdate::test_ensure_no_orphans_kills_after_interval tests/ga/test_update.py::TestUpdate::test_ensure_no_orphans_skips_if_no_orphans tests/ga/test_update.py::TestUpdate::test_evaluate_agent_health_ignores_installed_agent tests/ga/test_update.py::TestUpdate::test_evaluate_agent_health_raises_exception_for_restarting_agent tests/ga/test_update.py::TestUpdate::test_evaluate_agent_health_resets_with_new_agent tests/ga/test_update.py::TestUpdate::test_evaluate_agent_health_will_not_raise_exception_for_long_restarts tests/ga/test_update.py::TestUpdate::test_evaluate_agent_health_will_not_raise_exception_too_few_restarts tests/ga/test_update.py::TestUpdate::test_filter_blacklisted_agents tests/ga/test_update.py::TestUpdate::test_get_latest_agent tests/ga/test_update.py::TestUpdate::test_get_latest_agent_excluded tests/ga/test_update.py::TestUpdate::test_get_latest_agent_no_updates tests/ga/test_update.py::TestUpdate::test_get_latest_agent_skip_updates tests/ga/test_update.py::TestUpdate::test_get_latest_agent_skips_unavailable tests/ga/test_update.py::TestUpdate::test_get_pid_files tests/ga/test_update.py::TestUpdate::test_get_pid_files_returns_previous tests/ga/test_update.py::TestUpdate::test_is_clean_start_returns_false_for_current_agent tests/ga/test_update.py::TestUpdate::test_is_clean_start_returns_false_for_exceptions tests/ga/test_update.py::TestUpdate::test_is_clean_start_returns_true_sentinal_agent_is_not_current tests/ga/test_update.py::TestUpdate::test_is_clean_start_returns_true_when_no_sentinal tests/ga/test_update.py::TestUpdate::test_is_orphaned_returns_false_if_parent_exists tests/ga/test_update.py::TestUpdate::test_is_orphaned_returns_true_if_parent_does_not_exist tests/ga/test_update.py::TestUpdate::test_is_orphaned_returns_true_if_parent_is_init tests/ga/test_update.py::TestUpdate::test_load_agents tests/ga/test_update.py::TestUpdate::test_load_agents_does_reload tests/ga/test_update.py::TestUpdate::test_load_agents_sorts tests/ga/test_update.py::TestUpdate::test_purge_agents tests/ga/test_update.py::TestUpdate::test_run tests/ga/test_update.py::TestUpdate::test_run_clears_sentinal_on_successful_exit tests/ga/test_update.py::TestUpdate::test_run_emits_restart_event tests/ga/test_update.py::TestUpdate::test_run_keeps_running tests/ga/test_update.py::TestUpdate::test_run_latest tests/ga/test_update.py::TestUpdate::test_run_latest_captures_signals tests/ga/test_update.py::TestUpdate::test_run_latest_creates_only_one_signal_handler tests/ga/test_update.py::TestUpdate::test_run_latest_defaults_to_current tests/ga/test_update.py::TestUpdate::test_run_latest_exception_blacklists tests/ga/test_update.py::TestUpdate::test_run_latest_forwards_output tests/ga/test_update.py::TestUpdate::test_run_latest_nonzero_code_marks_failures tests/ga/test_update.py::TestUpdate::test_run_latest_polling_stops_at_failure tests/ga/test_update.py::TestUpdate::test_run_latest_polling_stops_at_success tests/ga/test_update.py::TestUpdate::test_run_latest_polls_and_waits_for_success tests/ga/test_update.py::TestUpdate::test_run_leaves_sentinal_on_unsuccessful_exit tests/ga/test_update.py::TestUpdate::test_run_stops_if_orphaned tests/ga/test_update.py::TestUpdate::test_run_stops_if_update_available tests/ga/test_update.py::TestUpdate::test_set_agents_sets_agents tests/ga/test_update.py::TestUpdate::test_set_agents_sorts_agents tests/ga/test_update.py::TestUpdate::test_set_sentinal tests/ga/test_update.py::TestUpdate::test_set_sentinal_writes_current_agent tests/ga/test_update.py::TestUpdate::test_shutdown tests/ga/test_update.py::TestUpdate::test_shutdown_ignores_exceptions tests/ga/test_update.py::TestUpdate::test_shutdown_ignores_missing_sentinal_file tests/ga/test_update.py::TestUpdate::test_upgrade_available_handles_missing_family tests/ga/test_update.py::TestUpdate::test_upgrade_available_includes_old_agents tests/ga/test_update.py::TestUpdate::test_upgrade_available_purges_old_agents tests/ga/test_update.py::TestUpdate::test_upgrade_available_returns_true_on_first_use tests/ga/test_update.py::TestUpdate::test_upgrade_available_skips_if_too_frequent tests/ga/test_update.py::TestUpdate::test_upgrade_available_skips_if_when_no_new_versions tests/ga/test_update.py::TestUpdate::test_upgrade_available_skips_when_no_versions tests/ga/test_update.py::TestUpdate::test_upgrade_available_skips_when_updates_are_disabled tests/ga/test_update.py::TestUpdate::test_upgrade_available_sorts tests/ga/test_update.py::TestUpdate::test_write_pid_file tests/ga/test_update.py::TestUpdate::test_write_pid_file_ignores_exceptions
azurelinuxagent/common/event.py azurelinuxagent/ga/exthandlers.py azurelinuxagent/ga/update.py
azurelinuxagent/common/event.py azurelinuxagent/ga/exthandlers.py azurelinuxagent/ga/update.py